11.10.6 ngx_http_finalize_request

ngx_http_finalize_request方法是开发HTTP模块时最常使用的结束请求方法,在第3章中早已介绍过它的简单用法。事实上,ngx_http_finalize_request方法被HTTP框架设计得极为复杂,各种结束请求的场景都被它考虑到了,下面将详细讲述这个方法究竟做了些什么。首先回顾一下它的定义。


void ngx_http_finalize_request(ngx_http_request_t*r,ngx_int_t rc)


其中,参数r就是当前请求,它可能是派生出的子请求,也可能是客户端发来的原始请求。后面的参数rc就非常复杂了,它既可能是NGX_OK、NGX_ERROR、NGX_AGAIN、NGX_DONE、NGX_DECLINED这种系统定义的返回值,又可能是类似NGX_HTTP_REQUEST_TIME_OUT这样的HTTP响应码,因此,ngx_http_finalize_request方法的流程异常复杂。学习如何正确地使用ngx_http_finalize_request方法非常关键,因为会涉及不同动作导致的引用计数增加、异常情况下自动构造响应、未发送完所有响应时自动向事件框架添加写事件回调方法ngx_http_writer等各种场景。大多数情况下,我们都会把其他Nginx方法的返回值作为rc参数来调用ngx_http_finalize_request方法,但如果要编写复杂的HTTP模块,还是需要清晰地认识ngx_http_finalize_request方法的工作原理。

下面把ngx_http_finalize_request方法的主要流程简化为了17个主要步骤,如图11-26所示。

11.10.6 ngx_http_finalize_request - 图1

图 11-26 ngx_http_finalize_request方法的流程图

下面解释一下ngx_http_finalize_request方法所做的工作。

1)首先检查rc参数。如果rc为NGX_DECLINED,则跳到第2步执行;如果rc为NGX_DONE,则跳到第4步执行;除此之外,都继续执行第5步。

2)NGX_DECLINED参数表示请求还需要按照11个HTTP阶段继续处理下去,参考11.6节的内容可以知道,这时需要继续调用ngx_http_core_run_phases方法处理请求。这一步中首先会把ngx_http_request_t结构体的write_event_handler设为ngx_http_core_run_phases方法。同时,将请求的content_handler成员置为NULL空指针,11.6节已介绍过这个成员,它是一种用于在NGX_HTTP_CONTENT_PHASE阶段处理请求的方式,将其设置为NULL是为了让ngx_http_core_content_phase方法(11.6.4节介绍)可以继续调用NGX_HTTP_CONTENT_PHASE阶段的其他处理方法。

3)调用ngx_http_core_run_phases方法继续处理请求,ngx_http_finalize_request方法结束。

4)NGX_DONE参数表示不需要做任何事,直接调用ngx_http_finalize_connection方法,之后ngx_http_finalize_request方法结束。当某一种动作(如接收HTTP请求包体)正常结束而请求还有业务要继续处理时,多半都是传递NGX_DONE参数。由11.10.4节我们知道,这个ngx_http_finalize_connection方法还会去检查引用计数情况,并不一定会销毁请求。

5)检查当前请求是否为subrequest子请求,如果不是,则跳到第6步执行;如果是子请求,那么调用post_subrequest下的handler回调方法。在第6章中曾经介绍过subrequest的用法,可以看到post_subrequest正是此时被调用的。

6)第1步只是把rc参数的两种特殊值处理掉了,现在又需要再次检查rc参数了。如果rc值为NGX_ERROR、NGX_HTTP_REQUEST_TIME_OUT、NGX_HTTP_CLOSE、NGX_HTTP_CLIENT_CLOSED_REQUEST,或者这个连接的error标志为1,那么跳到第7步执行;如果rc为NGX_HTTP_CREATED、NGX_HTTP_NO_CONTENT或者大于或等于NGX_HTTP_SPECIAL_RESPONSE,则表示请求的动作是上传文件,或者HTTP模块需要HTTP框架构造并发送响应码大于或等于300以上的特殊响应,这时跳到第8步执行;其他情况下,直接跳到第12步执行。

7)这一步直接调用ngx_http_terminate_request方法强制结束请求,同时,ngx_http_finalize_request方法结束。

8)检查当前请求的main是否指向自己,如果是,这个请求就是来自客户端的原始请求(非子请求),这时检查读/写事件的timer_set标志位,如果timer_set为1,则表明事件在定时器中,需要调用ngx_del_timer方法把读/写事件从定时器中移除。

9)设置读/写事件的回调方法为ngx_http_request_handler方法,这个方法在11.6节中介绍过,它会继续处理HTTP请求。

10)调用ngx_http_special_response_handler方法,该方法负责根据rc参数构造完整的HTTP响应。为什么可以在这一步中构造这样的响应呢?回顾一下第7步,这时rc要么是表示上传成功的201或者204,要么就是表示异步的300以上的响应码,对于这些情况,都是可以让HTTP框架独立构造响应包的。

11)再次调用ngx_http_finalize_request方法结束请求,不过这时的rc参数实际上是第10步ngx_http_special_response_handler方法的返回值。

12)再次检查请求的main成员是否指向自己,即当前请求是否为原始请求。如果不是客户端发来的原始请求,跳到13步继续执行;如果是原始请求,那么还需要检查out缓冲区内是否还有没发送完的响应,如果有,则跳到第14步继续执行,如果没有,则可以结束请求了,此时跳到第16步。

13)由于当前请求是子请求,那么正常情况下需要跳到它的父请求上,激活父请求继续向下执行,所以这一步首先根据ngx_http_request_t结构体的parent成员找到父请求,再构造一个ngx_http_posted_request_t结构体把父请求放置其中,最后把该结构体添加到原始请求的posted_requests链表中,这样11.7节中介绍过的ngx_http_run_posted_requests方法就会在图11-12描述的流程中调用父请求的write_event_handler方法了。

14)在11.9节中多次讲到,当HTTP响应过大,无法一次性发送给客户端时,需要调用ngx_http_finalize_request方法结束请求,而该方法会把11.9.3节介绍的ngx_http_writer方法注册给epoll和定时器,当连接再次可写时就会继续发送剩余的响应,这些工作就是在第14、第15步中完成的。这一步先把请求的write_event_handler成员设为ngx_http_writer方法。

15)如果写事件的delayed标志位为0,就把写事件添加到定时器中,超时时间就是nginx.conf文件中的send_timeout配置项;当然,如果delayed为1,则表示限制发送速度,从11.9.2节可以看出,在需要限速时,根据计算得到的超时时间已经把写事件添加到定时器中了。再调用ngx_handle_write_event方法把写事件添加到epoll中。

16)到了这里真的要结束请求了。首先判断读/写事件的timer_set标志位,如果timer_set为1,则需要把相应的读/写事件从定时器中移除。

17)调用11.10.4节中介绍过的ngx_http_finalize_connection方法结束请求。

事实上,ngx_http_finalize_request方法的分支流程远不止上面的17步,为了让读者清晰地理解其主要工作,许多不太重要的分支都从图10-26中去除了。到此,读者应当对ngx_http_finalize_request方法有了相当全面的了解,这时再开发HTTP模块就可以灵活地指定rc参数了。