11.6.4 ngx_http_core_content_phase

ngx_http_core_content_phase是NGX_HTTP_CONTENT_PHASE阶段的checker方法,可以说它是我们开发HTTP模块时最常用的一个阶段了。顾名思义,NGX_HTTP_CONTENT_PHASE阶段用于真正处理请求的内容。其余10个阶段中各HTTP模块的处理方法都是放在全局的ngx_http_core_main_conf_t结构体中的,也就是说,它们对任何一个HTTP请求都是有效的。但在NGX_HTTP_CONTENT_PHASE阶段却很自然地有另一种需求,有的HTTP模块可能仅希望在这个处理请求内容的阶段,仅仅针对某种请求唯一生效,而不是对所有请求生效。例如,仅当请求的URI匹配了配置文件中的某个location块时,再根据location块下的配置选择一个HTTP模块执行它的handler处理方法,并以此替代NGX_HTTP_CONTENT_PHASE阶段的其他handler方法(这些handler方法对于该请求将得不到执行)。

既然我们希望请求在NGX_HTTP_CONTENT_PHASE阶段的handler方法仅与location相关,那么就肯定与ngx_http_core_loc_conf_t结构体相关了,注意handler成员:


struct ngx_http_core_loc_conf_s{

……

ngx_http_handler_pt handler;

……


这个handler成员属于nginx.conf中匹配了请求的location块下配置的HTTP模块(当然,如果请求匹配的location块下没有配置HTTP模块处理请求,那么这个handler指针将为NULL空指针)。回顾一下第3章中的ngx_http_mytest方法,它正是在某个location下检测到mytest配置项后,取到当前location下的ngx_http_core_loc_conf_t结构体,并把handler成员设置为希望在NGX_HTTP_CONTENT_PHASE阶段处理请求的ngx_http_mytest_handler方法的。

实际上,为了加快处理速度,HTTP框架又在ngx_http_request_t结构体中增加了一个成员content_handler(参见11.3.1节),在NGX_HTTP_FIND_CONFIG_PHASE阶段就会把它设为匹配了请求URI的location块中对应的ngx_http_core_loc_conf_t结构体的handler成员(参见Nginx源代码的ngx_http_update_location_config方法)。

以上所述是NGX_HTTP_CONTENT_PHASE阶段的特殊之处,当然,它还可以像其余10个阶段一样具备全局生效的handler方法,但如果设置了content_handler方法,会优先以content_handler为准,如图11-11所示。

11.6.4 ngx_http_core_content_phase - 图1

图 11-11 ngx_http_core_content_phase方法的流程

下面详细介绍一下ngx_http_core_content_phase方法是如何处理NGX_HTTP_CONTENT_PHASE阶段的请求的。

1)首先检测ngx_http_request_t结构体的content_handler成员是否为空,其实就是看在NGX_HTTP_FIND_CONFIG_PHASE阶段匹配了URI请求的location内,是否有HTTP模块把处理方法设置到了ngx_http_core_loc_conf_t结构体的handler成员中。如果content_handler为空,则跳到第2步开始执行全局有效的handler方法;否则仅执行content_handler方法,看看源代码中做了些什么,如下所示。


r->write_event_handler=ngx_http_request_empty_handler;

ngx_http_finalize_request(r,r->content_handler(r));


其中,首先设置ngx_http_request_t结构体的write_event_handler成员为不做任何事的ngx_http_request_empty_handler方法,也就是告诉HTTP框架再有可写事件时就调用ngx_http_request_empty_handler直接把控制权交还给事件模块。为何要这样做呢?因为HTTP框架在这一阶段调用HTTP模块处理请求就意味着接下来只希望该模块处理请求,先把write_event_handler强制转化为ngx_http_request_empty_handler,可以防止该HTTP模块异步地处理请求时却有其他HTTP模块还在同时处理可写事件、向客户端发送响应。接下来调用content_handler方法处理请求,并把它的返回值作为参数传递给ngx_http_finalize_request方法来结束请求。ngx_http_finalize_request方法是非常复杂的,它会根据引用计数来确定自己的行为,具体参见11.10.6节。

2)在没有content_handler方法时,又回到了我们惯用的方式,首先根据phase_handler序号调用handler处理方法,检测它的返回值:当返回值为NGX_DECLINED时跳到第4步,否则跳到第3步执行。

3)如果NGX_HTTP_CONTENT_PHASE阶段中全局的handler方法没有返回NGX_DECLINED,则意味着不再执行该阶段的其他handler方法。因此,这时简单地以handler方法作为参数调用ngx_http_finalize_request结束请求即可。同时,ngx_http_core_content_phase方法返回NGX_OK,表示归还控制权给事件模块。

4)虽然handler方法返回了NGX_DECLINED,表示希望执行本阶段的下一个handler方法,但是当前的handler方法是否已经是最后一个handler方法了呢?这需要进行检测,首先转到数组中的下一个handler方法,检测其checker方法是否存在,若存在,则跳到第5步执行,若不存在,则结束请求,但需要根据URI确定返回什么样的HTTP响应,如果URI是以“/”结尾,则跳到第6步执行,否则跳到第7步执行。

5)既然handler方法返回NGX_DECLINED希望执行下一个handler方法,那么这一步把请求的phase_handler序号加1,ngx_http_core_content_phase方法返回NGX_AGAIN,表示希望HTTP框架立刻执行下一个handler方法。

6)以NGX_HTTP_FORBIDDEN作为参数调用ngx_http_finalize_request方法,表示结束请求并返回403错误码。同时,ngx_http_core_content_phase方法返回NGX_OK,表示交还控制权给事件模块。

7)以NGX_HTTP_NOT_FOUND作为参数调用ngx_http_finalize_request方法,表示结束请求并返回404错误码。同时,ngx_http_core_content_phase方法返回NGX_OK,表示交还控制权给事件模块。

NGX_HTTP_CONTENT_PHASE阶段是各HTTP模块最常介入的阶段。只有对ngx_http_core_content_phase方法的流程足够熟悉,才能实现复杂的功能。

注意 从ngx_http_core_content_phase方法中可以看到,请求在第10个阶段NGX_HTTP_CONTENT_PHASE后,并没有去调用第11个阶段NGX_HTTP_LOG_PHASE的处理方法,通过比较11.6节的其他checker方法,就会发现它与之前的方法都不同。事实上,记录访问日志是必须在请求将要结束时才能进行的,因此,NGX_HTTP_LOG_PHASE阶段的回调方法在11.10.2节介绍的ngx_http_free_request方法中才会调用到。