11.9.2 ngx_http_output_filter

ngx_http_output_filter方法用于发送响应包体,它的第2个参数就是用于存放响应包体的缓冲区,如下所示。


ngx_int_t ngx_http_write_filter(ngx_http_request_tr,ngx_chain_tin)


其中第2个参数in在第6章中已有过详细的介绍,这里不再赘述。用于过滤包体的HTTP模块将以ngx_http_next_body_filter作为链表指针连接成一个流水线,ngx_http_output_filter方法在发送包体时会依次调用各个过滤包体方法,其中最后一个过滤包体方法就是11.9.1节中介绍过的ngx_http_write_filter方法,它属于ngx_http_write_filter_module模块。

本节与ngx_http_send_header方法的介绍一样,不会讨论每个过滤模块的功能,我们只看最后一个包体过滤模块是怎样发送响应包体的。在图11-20中,ngx_http_write_filter方法展示了HTTP框架是如何开始发送HTTP响应包体的。

11.9.2 ngx_http_output_filter - 图1

图 11-20 ngx_http_write_filter方法的流程图

图11-20中描述的ngx_http_write_filter方法主要有13个步骤,下面详细介绍这些步骤到底是如何工作的。

1)首先检查请求的连接上ngx_connection_t结构体的error标志位,如果error为1表示请求出错,那么直接返回NGX_ERROR。

2)找到请求的ngx_http_request_t结构体中存放的等待发送的缓冲区链表out,遍历这个ngx_chain_t类型的缓冲区链表,计算出out缓冲区共占用了多大的字节数,为第9步发送响应做准备。

注意 这个out链表通常都保存着待发送的响应。例如,在调用ngx_http_send_header方法时,如果HTTP响应头部过大导致无法一次性发送完,那么剩余的响应头部就会在out链表中。

3)ngx_http_write_filter方法的第2个参数in就是本次要发送的缓冲区链表(正是由HTTP模块构造、传递),本步骤将类似第2步遍历这个ngx_chain_t类型的缓存链表in,将in中的缓冲区加入到out链表的末尾,并计算out缓冲区共占用多大的字节数,为第9步发送响应做准备。

在第2、第3步的遍历过程中,会检查缓冲区中每个ngx_buf_t块的3个标志位:flush、recycled、last_buf,如果这3个标志位同时为0(即待发送的out链表中没有一个缓冲区表示响应已经结束或需要立刻发送出去),而且本次要发送的缓冲区in虽然不为空,但以上两步骤中计算出的待发送响应的大小又小于配置文件中的postpone_output参数,那么说明当前的缓冲区是不完整的且没有必要立刻发送,于是跳到第13步直接返回NGX_OK。

4)取出nginx.conf文件中匹配请求的sendfile_max_chunk配置项(如它属于某个location块下的配置项),为第9步计算发送响应的速度做准备。

首先检查连接上写事件的标志位delayed,如果delayed为1,则表示这一次的epoll调度中请求仍需要减速,是不可以发送响应的,delayed为1指明了响应需要延迟发送,这时跳到第5步执行;如果delayed为0,表示本次不需要减速,那么再检查ngx_http_request_t结构体中的limit_rate发送响应的速率,如果limit_rate为0,表示这个请求不需要限制发送速度,直接跳到第9步执行;如果limit_rate大于0,则说明发送响应的速度不能超过limit_rate指定的速度,这时跳到第6步执行。

5)将客户端对应的ngx_connection_t结构体中的buffered标志位放上NGX_HTTP_WRITE_BUFFERED宏,同时返回NGX_AGAIN,这是在告诉HTTP框架out缓冲区中还有响应等待发送。

6)ngx_http_request_t结构体中的limit_rate成员表示发送响应的最大速率,当它大于0时,表示需要限速,首先需要计算当前请求的发送速度是否已经达到限速条件。

这里需要解释第2章中介绍过的nginx.conf文件里的两个配置项:limit_rate和limit_rate_after。limit_rate表示每秒可以发送的字节数,超过这个数字就需要限速;然而,限速这个动作必须是在发送了limit_rate_after字节的响应后才能生效(对于小响应包的优化设计)。下面看看这一步是如何使用这两个配置项来计算限速的,如下所示。


limit=r->limit_rate*(ngx_time()-r->start_sec+1)

-(c->sent-clcf->limit_rate_after);


第9章已介绍过ngx_time()方法,它取出了当前时间,而start_sec表示开始接收到客户端请求内容的时间,c->sent表示这条连接上已经发送了的HTTP响应长度,这样计算出的变量limit就表示本次可以发送的字节数了。如果limit小于或等于0,它表示这个连接上的发送响应速度已经超出了limit_rate配置项的限制,所以本次不可以继续发送,跳到第7步执行;如果limit大于0,表示本次可以发送limit字节的响应,那么跳到第9步开始发送响应。

7)由于达到发送响应的速度上限,这时将连接上写事件的delayed标志位置为1。

8)将写事件加入定时器中,其中超时时间要根据第7步算出的limit来计算,如下所示:


ngx_add_timer(c->write,(ngx_msec_t)(-limit*1000/r->limit_rate+1));


limit是已经超发的字节数,它是0或者负数。这个定时器的超时时间是超发字节数按照limit_rate速率算出需要等待的时间再加上1毫秒,它可以使Nginx定时器准确地在允许发送响应时激活请求。之后转到第5步执行。

9)本步将把响应发送给客户端。然而,缓冲区中的响应可能非常大,那么这一次应该发送多少字节呢?这要根据第6步计算出的limit变量,以及第4步取得的配置项sendfile_max_chunk来计算,同时要根据第2、第3步遍历缓冲区计算出的待发送字节数来决定,这3个值中的最小值即作为本次发送的响应长度。

发送响应后再次检查请求的limit_rate标志位,如果limit_rate为0,则表示不需要限速,跳到第12步执行;如果limit_rate大于0,则表示需要限速,跳到第10步执行。

10)再次按照第6步中的方法计算刚发送了部分响应后,请求的发送速率是否达到limit_rate上限,如果不需要减速就直接跳到第12步;否则继续执行第11步。

11)这时表示第9步发送的响应速度还是过快了,已经超发了一些响应,那么这里类似第8步,计算出至少要经过多少毫秒后才可以继续发送,调用ngx_add_timer方法将写事件按照上面计算出的毫秒作为超时时间添加到定时器中。同时,把写事件的delayed标志位置为1。

12)重置ngx_http_request_t结构体的out缓冲区,把已经发送成功的缓冲区归还给内存池。如果out链表中还有剩余的没有发送出去的缓冲区,则添加到out链表头部,跳到第5步执行;如果已经将out链表中的所有缓冲区都发送给客户端了,则执行第13步。

13)返回NGX_OK表示成功。

以上较为详尽地描述了负责实际发送响应的ngx_http_write_filter方法是怎样工作的,包括如何更新请求里的out缓冲区,如何根据限速条件以及配置文件中的sendfile_max_chunk参数决定一次可以发送多少字节的响应。

ngx_http_send_header方法最终会调用ngx_http_write_filter方法来发送响应头部,而ngx_http_output_filter方法最终也是调用ngx_http_write_filter方法来发送响应包体的,同样,ngx_http_output_filter也有可能得到返回值NGX_AGAIN(图11-20的第5步),它表示还有未发送的响应缓冲区在out成员中。这时,需要以NGX_AGAIN作为参数调用ngx_http_finalize_request方法,该方法将把写事件的回调方法设为ngx_http_writer方法,并由它来把剩下的响应全部发送给客户端。