11.9.3 ngx_http_writer
本节介绍的ngx_http_writer方法对各个HTTP模块而言是不可见的,但实际上它非常重要,因为无论是ngx_http_send_header还是ngx_http_output_filter方法,它们在调用时一般都无法发送全部的响应,剩下的响应内容都得靠ngx_http_writer方法来发送。如何把ngx_http_writer方法设置为请求写事件的回调方法呢?这部分内容将在11.10.6节中介绍,此处关注的重点是ngx_http_writer方法在后台究竟做了些什么。图11-21是ngx_http_writer方法的流程图,如果这个请求的连接上可写事件被触发,也就是TCP的滑动窗口在告诉Nginx进程可以发送响应了,这时ngx_http_writer方法就开始工作了。
下面将详细介绍图11-21中的7个步骤。
1)首先检查连接上写事件的timedout标志位,如果timedout为0,则表示写事件未超时,跳到第5步执行;如果timedout为1,则表示当前的写事件已经超时,这时有两种可能性:第一种,由于网络异常或者客户端长时间不接收响应,导致真实的发送响应超时;第二种,由于上一次发送响应时发送速率过快,超过了请求的limit_rate速率上限,而上节的ngx_http_write_filter方法就会设置一个超时时间将写事件添加到定时器中,这时本次的超时只是由限速导致,并非真正超时(结合图11-20理解)。那么,如何判断这个超时是真的超时还是出于限速的考虑呢?这要看事件的delayed标志位。从图11-20中可以看出,如果是限速把写事件加入定时器,一定会把delayed标志位置为1,如其中的第7步和第11步。如果写事件的delayed标志位为0,那就是真的超时了,这时调用ngx_http_finalize_request方法结束请求,传入的参数是NGX_HTTP_REQUEST_TIME_OUT,表示需要向客户端发送408错误码;如果delayed标志位为1,则继续执行第2步。
2)既然当前事件的超时是由限速引起的,那么此时可以把写事件的timedout标志位和delayed标志位都重置为0。
再检查写事件的ready标志位,如果为1,则表示在与客户端的TCP连接上可以发送数据,跳到第5步执行;如果为0,则表示暂不可发送数据,跳到第3步执行。
3)将写事件添加到定时器中,这里的超时时间就是配置文件中的send_timeout参数,与限速功能无关。
4)调用ngx_handle_write_event方法将写事件添加到epoll等事件收集器中,同时ngx_http_writer方法结束。
图 11-21 ngx_http_writer方法的流程图
5)调用ngx_http_output_filter方法发送响应,其中第2个参数(也就是表示需要发送的缓冲区)为NULL指针。这意味着,需要调用各包体过滤模块处理out缓冲区中的剩余内容,最后调用ngx_http_write_filter方法把响应发送出去。
发送响应后,查看ngx_http_request_t结构体中的buffered和postponed标志位,如果任一个不为0,则意味着没有发送完out中的全部响应,这时跳到第3步执行;请求main指针指向请求自身,表示这个请求是原始请求,再检查与客户端间的连接ngx_connection_t结构体中的buffered标志位,如果buffered不为0,同样表示没有发送完out中的全部响应,仍然跳到第3步执行;除此以外,都表示out中的全部响应皆发送完毕,跳到第6步执行。
6)将请求的write_event_handler方法置为ngx_http_request_empty_handler,也就是说,如果这个请求的连接上再有可写事件,将不做任何处理。
7)调用ngx_http_finalize_request方法结束请求,其中第2个参数传入的是ngx_http_output_filter方法的返回值。
注意 ngx_http_writer方法仅用于在后台发送响应到客户端。