10.6 HTTP请求的11个处理阶段

Nginx为什么要把HTTP请求的处理过程分为多个阶段呢?这要从第8章介绍过的“一切皆模块”说起。Nginx的模块化设计使得每一个HTTP模块可以仅专注于完成一个独立的、简单的功能,而一个请求的完整处理过程可以由无数个HTTP模块共同合作完成。这种设计有非常好的简单性、可测试性、可扩展性,然而,当多个HTTP模块流水式地处理同一个请求时,单一的处理顺序是无法满足灵活性需求的,每一个正在处理请求的HTTP模块很难灵活、有效地指定下一个HTTP处理模块是哪一个。而且,不划分处理阶段也会让HTTP请求的完整处理流程难以管理,每一个HTTP模块也很难正确地将自己插入到完整流程中的合适位置中。

因此,HTTP框架依据常见的处理流程将处理阶段划分为11个阶段,其中每个处理阶段都可以由任意多个HTTP模块流水式地处理请求。先来回顾一下第3章中曾经提到过的ngx_http_phases阶段的定义,如下所示。


typedef enum{

//在接收到完整的HTTP头部后处理的HTTP阶段

NGX_HTTP_POST_READ_PHASE=0,

/在将请求的URI与location表达式匹配前,修改请求的URI(所谓的重定向)是一个独立的HTTP阶段/

NGX_HTTP_SERVER_REWRITE_PHASE,

/根据请求的URI寻找匹配的location表达式,这个阶段只能由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为/

NGX_HTTP_FIND_CONFIG_PHASE,

/在NGX_HTTP_FIND_CONFIG_PHASE阶段寻找到匹配的location之后再修改请求的URI/

NGX_HTTP_REWRITE_PHASE,

/这一阶段是用于在rewrite重写URL后,防止错误的nginx.conf配置导致死循环(递归地修改URI),因此,这一阶段仅由ngx_http_core_module模块处理。目前,控制死循环的方式很简单,首先检查rewrite的次数,如果一个请求超过10次重定向,就认为进入了rewrite死循环,这时在NGX_HTTP_POST_REWRITE_PHASE阶段就会向用户返回500,表示服务器内部错误/

NGX_HTTP_POST_REWRITE_PHASE,

/表示在处理NGX_HTTP_ACCESS_PHASE阶段决定请求的访问权限前,HTTP模块可以介入的处理阶段/

NGX_HTTP_PREACCESS_PHASE,

//这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器

NGX_HTTP_ACCESS_PHASE,

/在NGX_HTTP_ACCESS_PHASE阶段中,当HTTP模块的handler处理函数返回不允许访问的错误码时(实际就是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),这里将负责向用户发送拒绝服务的错误响应。因此,这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾/

NGX_HTTP_POST_ACCESS_PHASE,

/这个阶段完全是为try_files配置项而设立的,当HTTP请求访问静态文件资源时,try_files配置项可以使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。这个功能完全是在NGX_HTTP_TRY_FILES_PHASE阶段中实现的/

NGX_HTTP_TRY_FILES_PHASE,

//用于处理HTTP请求内容的阶段,这是大部分HTTP模块最愿意介入的阶段

NGX_HTTP_CONTENT_PHASE,

/处理完请求后记录日志的阶段。例如,ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,使得每个HTTP请求处理完毕后会记录access_log访问日志/

NGX_HTTP_LOG_PHASE

}ngx_http_phases;


对于这11个处理阶段,有些阶段是必备的,有些阶段是可选的,当然也可以有多个HTTP模块同时介入同一阶段(这时,将会在一个阶段中按照这些HTTP模块的ctx_index顺序来依次执行它们提供的handler处理方法)。在10.6.1节中将会介绍这11个阶段共同适用的规则,在10.6.2节~10.6.12节则会描述这些具体的处理阶段。

注意 ngx_http_phases定义的11个阶段是有顺序的,必须按照其定义的顺序执行。同时也要意识到,并不是说一个用户请求最多只能经过11个HTTP模块提供的ngx_http_handler_pt方法来处理,NGX_HTTP_POST_READ_PHASE、NGX_HTTP_SERVER_REWRITE_PHASE、NGX_HTTP_REWRITE_PHASE、NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_ACCESS_PHASE、NGX_HTTP_CONTENT_PHASE、NGX_HTTP_LOG_PHASE这7个阶段可以包括任意多个处理方法,它们是可以同时作用于同一个用户请求的。而NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_REWRITE_PHASE、NGX_HTTP_POST_ACCESS_PHASE、NGX_HTTP_TRY_FILES_PHASE这4个阶段则不允许HTTP模块加入自己的ngx_http_handler_pt方法处理用户请求,它们仅由HTTP框架实现。

10.6.1 HTTP处理阶段的普适规则

下面先来看看HTTP阶段的定义,它包括checker检查方法和handler处理方法,如下所示。


typedef struct ngx_http_phase_handler_s ngx_http_phase_handler_t;

/一个HTTP处理阶段中的checker检查方法,仅可以由HTTP框架实现,以此控制HTTP请求的处理流程/

typedef ngx_int_t(ngx_http_phase_handler_pt)(ngx_http_request_tr,ngx_http_phase_handler_t*ph);

/由HTTP模块实现的handler处理方法,这个方法在第3章中曾经用ngx_http_mytest_handler方法实现过/

typedef ngx_int_t(ngx_http_handler_pt)(ngx_http_request_tr);

//注意:ngx_http_phase_handler_t结构体仅表示处理阶段中的一个处理方法

struct ngx_http_phase_handler_s{

/在处理到某一个HTTP阶段时,HTTP框架将会在checker方法已实现的前提下首先调用checker方法来处理请求,而不会直接调用任何阶段中的handler方法,只有在checker方法中才会去调用handler方法。因此,事实上所有的checker方法都是由框架中的ngx_http_core_module模块实现的,且普通的HTTP模块无法重定义checker方法/

ngx_http_phase_handler_pt checker;

/除ngx_http_core_module模块以外的HTTP模块,只能通过定义handler方法才能介入某一个HTTP处理阶段以处理请求/

ngx_http_handler_pt handler;

//将要执行的下一个HTTP处理阶段的序号

/next的设计使得处理阶段不必按顺序依次执行,既可以向后跳跃数个阶段继续执行,也可以跳跃到之前曾经执行过的某个阶段重新执行。通常,next表示下一个处理阶段中的第1个ngx_http_phase_handler_t处理方法/

ngx_uint_t next;

};


注意 通常,在任意一个ngx_http_phases阶段,都可以拥有零个或多个ngx_http_phase_handler_t结构体,其含义更接近于某个HTTP模块的处理方法。

一个http{}块解析完毕后将会根据nginx.conf中的配置产生由ngx_http_phase_handler_t组成的数组,在处理HTTP请求时,一般情况下这些阶段是顺序向后执行的,但ngx_http_phase_handler_t中的next成员使得它们也可以非顺序执行。ngx_http_phase_engine_t结构体就是所有ngx_http_phase_handler_t组成的数组,如下所示。


typedef struct{

/handlers是由ngx_http_phase_handler_t构成的数组首地址,它表示一个请求可能经历的所有ngx_http_handler_pt处理方法/

ngx_http_phase_handler_t*handlers;

/表示NGX_HTTP_SERVER_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法在handlers数组中的序号,用于在执行HTTP请求的任何阶段中快速跳转到NGX_HTTP_SERVER_REWRITE_PHASE阶段处理请求/

ngx_uint_t server_rewrite_index;

/表示NGX_HTTP_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法在handlers数组中的序号,用于在执行HTTP请求的任何阶段中快速跳转到NGX_HTTP_REWRITE_PHASE阶段处理请求/

ngx_uint_t location_rewrite_index;

}ngx_http_phase_engine_t;


可以看到,ngx_http_phase_engine_t中保存了在当前nginx.conf配置下,一个用户请求可能经历的所有ngx_http_handler_pt处理方法,这是所有HTTP模块可以合作处理用户请求的关键!这个ngx_http_phase_engine_t结构体是保存在全局的ngx_http_core_main_conf_t结构体中的,如下所示。


typedef struct{

/由下面各阶段处理方法构成的phases数组构建的阶段引擎才是流水式处理HTTP请求的实际数据结构/

ngx_http_phase_engine_t phase_engine;

/用于在HTTP框架初始化时帮助各个HTTP模块在任意阶段中添加HTTP处理方法,它是一个有11个成员的ngx_http_phase_t数组,其中每一个ngx_http_phase_t结构体对应一个HTTP阶段。在HTTP框架初始化完毕后,运行过程中的phases数组是无用的/

ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE+1];

……

}ngx_http_core_main_conf_t;


在ngx_http_core_main_conf_t中关于HTTP阶段有两个成员:phase_engine和phases,其中phase_engine控制运行过程中一个HTTP请求所要经过的HTTP处理阶段,它将配合ngx_http_request_t结构体中的phase_handler成员使用(phase_handler指定了当前请求应当执行哪一个HTTP阶段);而phases数组更像一个临时变量,它实际上仅会在Nginx启动过程中用到,它的唯一使命是按照11个阶段的概念初始化phase_engine中的handlers数组。下面看一下ngx_http_phase_t的定义。


typedef struct{

//handlers动态数组保存着每一个HTTP模块初始化时添加到当前阶段的处理方法

ngx_array_t handlers;

}ngx_http_phase_t;


在HTTP框架的初始化过程中,任何HTTP模块都可以在ngx_http_module_t接口的postconfiguration方法中将自定义的方法添加到handler动态数组中,这样,这个方法就会最终添加到phase_engine中(注意,第3章中mytest模块并没有把ngx_http_mytest_handler方法加入到phases的handlers数组中,这是因为对于NGX_HTTP_CONTENT_PHASE阶段来说,还有另一种初始化方法,在10.6.11节中我们会介绍)。在第11章中可以看到这些HTTP阶段是如何执行的。

下面将会简要介绍这11个HTTP处理阶段,读者关注重点是每个阶段的checker方法都做了些什么。