11.6 处理HTTP请求
在接收到完整的HTTP头部后,已经拥有足够的必要信息开始在业务上处理HTTP请求了。本节将说明HTTP框架是如何召集负责具体功能的各HTTP模块合作处理请求的。在图11-4的第11步及图11-3的第10步中,最后都是通过调用ngx_http_process_request方法开始处理请求,本节将讨论ngx_http_process_request方法的流程,而且ngx_http_process_request方法只是处理请求的开始,对于基于事件驱动的异步HTTP框架来说,处理请求并不是一步可以完成的,所以我们也会讨论后续TCP连接上的回调方法ngx_http_request_handler的流程。首先来看看接收完HTTP头部后ngx_http_process_request方法所做的事情,如图11-5所示。
图 11-5 ngx_http_process_request处理HTTP请求的流程图
下面详细介绍图11-5中的8个步骤。
1)由于现在已经开始准备调用各HTTP模块处理请求了,因此不再存在接收HTTP请求头部超时的问题,那就需要从定时器中把当前连接的读事件移除了。检查读事件对应的timer_set标志位,为1时表示读事件已经添加到定时器中了,这时需要调用ngx_del_timer从定时器中移除读事件;如果timer_set标志位为0,则直接执行第2步。
2)从现在开始不会再需要接收HTTP请求行或者头部,所以需要重新设置当前连接读/写事件的回调方法。在这一步骤中,将同时把读事件、写事件的回调方法都设置为ngx_http_request_handler方法,在下面的图11-7中会介绍到这个方法,请求的后续处理都是通过ngx_http_request_handler方法进行的。
3)设置ngx_http_request_t结构体的read_event_handler方法为ngx_http_block_reading。前面11.3节中曾介绍过read_event_handler方法,当再次有读事件到来时,将会调用read_event_handler方法处理请求。而这里将它设置为ngx_http_block_reading方法,这个方法可认为不做任何事,它的意义在于,目前已经开始处理HTTP请求,除非某个HTTP模块重新设置了read_event_handler方法,否则任何读事件都将得不到处理,也可以认为读事件被阻塞了。
4)检查ngx_http_request_t结构体的internal标志位,如果internal为0,则继续执行第5步;如果internal标志位为1,则表示请求当前需要做内部跳转,将要把结构体中的phase_handler序号置为server_rewrite_index。先来回顾一下10.6.1节,注意ngx_http_phase_engine_t结构体中的handlers动态数组中保存了请求需要经历的所有回调方法,而server_rewrite_index则是handlers数组中NGX_HTTP_SERVER_REWRITE_PHASE处理阶段的第一个ngx_http_phase_handler_t回调方法所处的位置。
究竟handlers数组是怎么使用的呢?事实上,它要配合着ngx_http_request_t结构体的phase_handler序号使用,由phase_handler指定着请求将要执行的handlers数组中的方法位置。注意,handlers数组中的方法都是由各个HTTP模块实现的,这就是所有HTTP模块能够共同处理请求的原因。
在这一步骤中,把phase_handler序号设为server_rewrite_index,这意味着无论之前执行到哪一个阶段,马上都要重新从NGX_HTTP_SERVER_REWRITE_PHASE阶段开始再次执行,这是Nginx的请求可以反复rewrite重定向的基础。
5)当internal标志位为0时,表示不需要重定向(如刚开始处理请求时),将phase_handler序号置为0,意味着从ngx_http_phase_engine_t指定数组的第一个回调方法开始执行(参见10.6节,了解ngx_http_phase_engine_t是如何将各HTTP模块的回调方法构造成handlers数组的)。
6)设置ngx_http_request_t结构体的write_event_handler成员为ngx_http_core_run_phases方法。如同read_event_handler方法一样,在图11-7中可以看到write_event_handler方法是如何被调用的。
7)执行ngx_http_core_run_phases方法,其流程如图11-6所示。
图 11-6 ngx_http_core_run_phases方法的执行流程
8)调用ngx_http_run_posted_requests方法执行post请求,参见11.7节。
上述第7步调用了ngx_http_core_run_phases方法,该方法将开始调用各个HTTP模块共同处理请求。在第10章我们讨论过HTTP框架的初始化,在这一过程中是允许各个HTTP模块将自己的处理方法按照11个ngx_http_phases阶段添加到全局的ngx_http_core_main_conf_t结构体中的。下面简单地回顾一下它的定义,如下所示。
typedef struct{
……
//HTTP框架初始化后各个HTTP模块构造的处理方法将组成phase_engine
ngx_http_phase_engine_t phase_engine;
}ngx_http_core_main_conf_t;
typedef struct{
/由ngx_http_phase_handler_t结构体构成的数组,每一个数组成员代表着一个HTTP模块所添加的一个处理方法/
ngx_http_phase_handler_t*handlers;
……
}ngx_http_phase_engine_t;
typedef struct ngx_http_phase_handler_s ngx_http_phase_handler_t;
struct ngx_http_phase_handler_s{
/每个handler方法必须对应着一个checker方法,这个checker方法由HTTP框架实现/
ngx_http_phase_handler_pt checker;
//各个HTTP模块实现的方法
ngx_http_handler_pt handler;
……
};
可以看到,根据ngx_http_core_main_conf_t结构体的phase_engine成员即可依次调用各个HTTP模块来共同处理一个请求。下面看看图11-6中展示的ngx_http_core_run_phases方法的流程。
在图11-6中仅会执行每个ngx_http_phase_handler_t处理阶段的checker方法,而不会执行handler方法,其原因已在10.6节讲过,这是因为handler方法其实仅能在checker方法中被调用,而且checker方法由HTTP框架实现,所以可以控制各HTTP模块实现的处理方法在不同的阶段中采用不同的调用行为。再来简单地看一下调用的源代码。
void ngx_http_core_run_phases(ngx_http_request_t*r)
{
ngx_int_t rc;
ngx_http_phase_handler_t*ph;
ngx_http_core_main_conf_t*cmcf;
cmcf=ngx_http_get_module_main_conf(r,ngx_http_core_module);
ph=cmcf->phase_engine.handlers;
while(ph[r->phase_handler].checker){
rc=ph[r->phase_handler].checker(r,&ph[r->phase_handler]);
if(rc==NGX_OK){
return;
}
}
}
可以看到,ngx_http_request_t结构体中的phase_handler成员将决定执行到哪一阶段,以及下一阶段应当执行哪个HTTP模块实现的内容。在图11-5的第4步和第5步中可以看到请求的phase_handler成员会被重置,而HTTP框架实现的checker方法也会修改phase_handler成员的值。表11-1列出了HTTP框架实现的所有checker方法,如下所示。
我们在10.6节中曾经详细介绍过HTTP阶段。在11个阶段中其中7个是允许各个HTTP模块向阶段中任意添加自己实现的handler处理方法的,但同一个阶段中的所有handler处理方法都拥有相同的checker方法,见表11-1。我们知道,每个阶段中处理方法的返回值都会以不同的方式影响HTTP框架的行为,而在图11-6中也可以看到,checker方法在返回NGX_OK和其他值时也会导致不同的结果(每个checker方法的返回值实际上与handler处理方法的返回是相关的,参见10.6.2节~10.6.12节中对各个阶段的说明)。当checker方法的返回值非NGX_OK时,意味着向下执行phase_engine中的各处理方法;反之,当任何一个checker方法返回NGX_OK时,意味着把控制权交还给Nginx的事件模块,由它根据事件(网络事件、定时器事件、异步I/O事件等)再次调度请求。然而,一个请求多半需要Nginx事件模块多次地调度HTTP模块处理,这时就要看在图11-5中第2步设置的读/写事件的回调方法ngx_http_request_handler的功能了,如图11-7所示。
图 11-7 ngx_http_request_handler方法的执行流程
通常来说,在接收完HTTP头部后,是无法在一次Nginx框架的调度中处理完一个请求的。在第一次接收完HTTP头部后,HTTP框架将调度ngx_http_process_request方法开始处理请求,这时根据图11-6中的流程可以看到,如果某个checker方法返回了NGX_OK,则将会把控制权交还给Nginx框架。当这个请求上对应的事件再次触发时,HTTP框架将不会再调度ngx_http_process_request方法处理请求,而是由ngx_http_request_handler方法开始处理请求。下面来看看图11-7中列出的ngx_http_request_handler方法的流程:
1)ngx_http_request_handler是HTTP请求上读/写事件的回调方法。在ngx_event_t结构体表示的事件中,data成员指向了这个事件对应的ngx_connection_t连接,而根据11.3节中的内容可以看到,在HTTP框架的ngx_connection_t结构体中的data成员则指向了ngx_http_request_t结构体。毫无疑问,只有拥有了ngx_http_request_t结构体才可以处理HTTP请求,而第一个步骤是从事件中取出ngx_http_request_t结构体。
2)检查这个事件的write可写标志,如果write标志为1,则调用ngx_http_request_t结构体中的write_event_handler方法。注意,我们在ngx_http_handler方法中(即图11-5的第6步)已经将write_event_handler设置为ngx_http_core_run_phases方法,而一般我们开发的不太复杂的HTTP模块是不会重新设置write_event_handler方法的,因此,一旦有可写事件时,就会继续按照图11-6的流程执行ngx_http_core_run_phases方法,并继续按阶段调用各个HTTP模块实现的方法处理请求。
3)调用ngx_http_request_t结构体中的read_event_handler方法。注意比较第2步和第3步,如果一个事件的读写标志同时为1时,仅write_event_handler方法会被调用,即可写事件的处理优先于可读事件(这正是Nginx高性能设计的体现,优先处理可写事件可以尽快释放内存,尽量保持各HTTP模块少使用内存以提高并发能力)。
4)调用ngx_http_run_posted_requests方法执行post请求,参见11.7节。
以上重点讨论了ngx_http_process_request和ngx_http_request_handler这两个方法,其中ngx_http_process_request方法负责在接收完HTTP头部后,第一次与各个HTTP模块共同按阶段处理请求,而对于ngx_http_request_handler方法,如果ngx_http_process_request没能处理完请求,这个请求上的事件再次被触发,那就将由此方法继续处理了。
这两个方法的共通之处在于,它们都会先按阶段调用各个HTTP模块处理请求,再处理post请求。关于post请求的内容下文会介绍,而按阶段处理请求实际上就是图11-6中描述的流程,也就是通过每个阶段的checker方法来实现。在表11-1中可以看到,在各个HTTP模块能够介入的7个阶段中,实际上共享了4个checker方法:ngx_http_core_generic_phase、ngx_http_core_rewrite_phase、ngx_http_core_access_phase、ngx_http_core_content_phase,在10.6节中我们曾经简单地介绍过它们。
这4个checker方法的主要任务在于,根据phase_handler执行某个HTTP模块实现的回调方法,并根据方法的返回值决定:当前阶段已经完全结束了吗?下次要执行的回调方法是哪一个?究竟是立刻执行下一个回调方法还是先把控制权交还给epoll?下面通过介绍这4个checker方法来回答上述3个问题(其他checker方法仅由HTTP框架使用,这里不再详细介绍)。
11.6.1 ngx_http_core_generic_phase
从表11-1中可以看出,有3个HTTP阶段都使用了ngx_http_core_generic_phase作为它们的checker方法,这意味着任何试图在NGX_HTTP_POST_READ_PHASE、NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_LOG_PHASE这3个阶段处理请求的HTTP模块都需要了解ngx_http_core_generic_phase方法到底做了些什么。图11-8中描述了ngx_http_core_generic_phase方法的流程,可以看到,在调用了当前阶段的handler方法后,根据返回值的不同可能导致4种不同的结果。
图 11-8 ngx_http_core_generic_phase方法的执行流程
下面说明图11-8中所列的5个步骤。
1)首先调用HTTP模块实现的handler方法,这个方法的实现当然是不允许有阻塞操作的,它会立刻返回。根据它的返回值类型,将会有4种不同的结果:返回NGX_OK时直接跳转到第2步执行;返回NGX_DECLINED时跳转到第3步执行;返回NGX_AGAIN或者NGX_DONE时跳转到第4步执行;返回其他值时跳转到第5步执行。
2)如果HTTP模块实现的handler方法返回NGX_OK,这意味着当前阶段已经执行完毕,需要跳转到下一个阶段执行。例如,在NGX_HTTP_ACCESS_PHASE阶段中可能有两个HTTP模块都注册了回调方法,在执行第1个HTTP模块的回调方法时,如果它返回了NGX_OK,那么就不再执行第2个HTTP模块实现的回调方法了,而是跳转到下一个阶段(如NGX_HTTP_POST_ACCESS_PHASE)开始执行。注意,此时ngx_http_core_generic_phase方法会返回NGX_AGAIN,从图11-6中可以看到,非NGX_OK的返回值不会使HTTP框架把进程控制权交还给epoll等事件模块,而是会继续立刻执行请求的后续处理方法。
3)如果handler方法返回NGX_DECLINED,则会执行下一个回调方法。继续第2步中的例子,在NGX_HTTP_ACCESS_PHASE阶段,第1个HTTP模块的回调方法返回NGX_DECLINED后,下一个将要执行的方法仍然属于NGX_HTTP_ACCESS_PHASE阶段,即第2个HTTP模块实现的回调方法。注意,这时ngx_http_core_generic_phase返回的仍然是NGX_AGAIN,它意味着HTTP框架会紧接着继续执行请求的后续处理方法。
4)如果handler方法返回NGX_AGAIN或者NGX_DONE,则意味着刚才的handler方法无法在这一次调度中处理完这一个阶段,它需要多次调度才能完成,也就是说,刚刚执行过的handler方法希望:如果请求对应的事件再次被触发时,将由ngx_http_request_handler通过ngx_http_core_run_phases再次调用这个handler方法。直接返回NGX_OK会使得HTTP框架立刻把控制权交还给epoll事件框架,不再处理当前请求,唯有这个请求上的事件再次被触发才会继续执行。
5)如果handler方法返回了第2、第3、第4步中以外的返回值,则调用ngx_http_finalize_request结束请求。ngx_http_finalize_request方法中的参数就是handler方法的返回值,其影响参见11.10.6节。
当我们开发的HTTP模块试图介入NGX_HTTP_POST_READ_PHASE、NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_LOG_PHASE这3个阶段处理请求时,实现的handler方法需要根据上述步骤决定返回值。ngx_http_core_generic_phase可以帮助我们较为简单地实现强大的异步无阻塞处理能力。