前言
Nginx作为一个http服务器,核心任务就是处理HTTP请求。在接收到请求时,Nginx框架首先解析http请求,将解析结果放在ngx_http_request中,由于http是在tcp上工作的,因此解析可能会持续一段时间。nginx用状态机完成对HTTP请求的异步解析。整个解析过程都是由Nginx框架代码完成,不需要用户介入。当解析得到完整的http请求后,就开始处理http请求。nginx对http请求的处理是分阶段进行的。对请求的处理也是用户主要介入nginx的地方。对于一个http请求,nginx主要分以下11个阶段进行处理:
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
这篇博文将简单介绍一下这些处理请求时,这些阶段是怎么介入进去的。
http请求的处理 – ngx_http_handler
http请求处理的入口是ngx_http_process_request函数:
void
ngx_http_process_request(ngx_http_request_t *r)
在ngx_http_process_request函数中,我们可以看到:
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
r->read_event_handler = ngx_http_block_reading;
ngx_http_handler(r);
也就是说,下次有读写事件在这个连接上发生时,将会调用ngx_http_request_handler处理函数。
static void
ngx_http_request_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
c = ev->data;
r = c->data;
ctx = c->log->data;
ctx->current_request = r;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http run request: \"%V?%V\"", &r->uri, &r->args);
if (ev->write) {
r->write_event_handler(r); //ngx_http_core_run_phases
} else {
r->read_event_handler(r);
}
ngx_http_run_posted_requests(c);
}
我们可以看到这个函数主要是通过调用r->read_event_handler和r->write_event_handler来完成任务。
而从ngx_http_process_request里面可以看到:
r->read_event_handler = ngx_http_block_reading;
r->write_event_handler在ngx_http_handler函数中被设置:
r->write_event_handler = ngx_http_core_run_phases;
由于处理http请求主要是处理写事件,因为接到http请求之后,后面需要做的就是将响应写到客户端。
因此我们可以看到,nginx处理http请求主要是在ngx_http_core_run_phases函数中完成:
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); //获得ngx_http_core_module模块的main_conf配置结构体
ph = cmcf->phase_engine.handlers;
while (ph[r->phase_handler].checker) { //逐一执行各个阶段的checker函数
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
可以看到,Nginx依次调用各个阶段的checker函数处理请求。请求的当前处理阶段是通过
r->phase_handler
来标识。
各个阶段处理函数的初始化
从前面可以知道,各个阶段的处理函数都在ngx_http_core_module模块的main_conf的phase_engine.handlers成员中。phase_engine.handlers是一个数组,数组类型是
struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker;
ngx_http_handler_pt handler;
ngx_uint_t next;
};
也就是说,每个元素包含三个成员,分别是checker函数,handler函数,和next。checker函数是对外的接口,handler一般是由checker函数调用,next表示下一个阶段被调用的首个处理函数在数组中的位置。因此对各个阶段处理函数的初始化就是对phase_engine.handlers是一个数组的初始化。这个初始化和两个函数相关:
static ngx_int_t
ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf);
static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
这两个函数都是在ngx_http_block函数中被调用的。
ngx_http_init_phases
static ngx_int_t
ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
cf->pool, 2, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
cf->pool, 4, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
if (ngx_array_init(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers,
cf->pool, 1, sizeof(ngx_http_handler_pt))
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
这个函数很简单,主要是对几个数组进行初始化。
ngx_http_core_module的main_conf配置结构体中的phases是一个数组
ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];
数组中的每个元素的类型是
typedef struct {
ngx_array_t handlers;
} ngx_http_phase_t;
所以phases是一个二维数组。phase数组中的每个元素表示对应一个阶段的handler函数数组。这是为了后面初始化phase_engine.handlers做准备的。
Nginx模块编写用户可以通过向具体的阶段添加handler函数来实现http请求的处理干涉。一般的方法就是向phases中对应的某个阶段的handler数组中添加。如要向第( i )阶段添加一个handler,则将handler函数 push 到phases[i]数组中就可以了,这个介入一般是通过调用http模块的postconfiguration成员函数实现。
我们从这个函数可以看到,函数中只初始化了7个阶段对应的数组,而不是所有的11个阶段的数组都被初始化。因此,可以发现,Nginx当前只支持用户介入处理http请求的七个阶段:
1. NGX_HTTP_POST_READ_PHASE
2. NGX_HTTP_SERVER_REWRITE_PHASE
3. NGX_HTTP_REWRITE_PHASE
4. NGX_HTTP_PREACCESS_PHASE
5. NGX_HTTP_ACCESS_PHASE
6. NGX_HTTP_CONTENT_PHASE
7. NGX_HTTP_LOG_PHASE
ngx_http_init_phase_handlers
这个函数中主要是用上一个函数,以及各个http模块通过调用自己的postconfiguration函数初始化后的cmcf->phase二维数组对cmcf->phase_engine.handlers数组进行初始化,cmcf->phase_engine.handlers是处理http请求是实际使用的东西。这里需要注意的是,cmcf->phase_engine.handlers是一个一维数组。下面我们具体看一下这个函数的实现:
//为cmcf->phase_engine.handlers分配空间
static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
ngx_int_t j;
ngx_uint_t i, n;
ngx_uint_t find_config_index, use_rewrite, use_access;
ngx_http_handler_pt *h;
ngx_http_phase_handler_t *ph;
ngx_http_phase_handler_pt checker;
cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
find_config_index = 0;
use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
n += cmcf->phases[i].handlers.nelts; // 每个阶段中包含的模块处理函数个数
}
ph = ngx_pcalloc(cf->pool,
n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
if (ph == NULL) {
return NGX_ERROR;
}
cmcf->phase_engine.handlers = ph;
这部分代码的主要功能是为cmcf->phase_engine.handlers分配空间。其他功能我们在后面详细介绍各个阶段的功能时在说。
//用cmcf->phase二维数组对cmcf->phase_engine.handlers数组进行初始化
n = 0;
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
//h指向当前阶段的handler数组
h = cmcf->phases[i].handlers.elts;
switch (i) {
// 凡是有continue语句的,表示这个阶段的处理方法只能由HTTP框架实现
case NGX_HTTP_SERVER_REWRITE_PHASE:
if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
cmcf->phase_engine.server_rewrite_index = n;
}
checker = ngx_http_core_rewrite_phase;
break;
case NGX_HTTP_FIND_CONFIG_PHASE: // 不允许假如自定义的模块处理方法
find_config_index = n;
ph->checker = ngx_http_core_find_config_phase;
n++;
ph++;
continue;
case NGX_HTTP_REWRITE_PHASE:
if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
cmcf->phase_engine.location_rewrite_index = n;
}
checker = ngx_http_core_rewrite_phase;
break;
case NGX_HTTP_POST_REWRITE_PHASE: // 不允许假如模块自定义的处理方法
if (use_rewrite) {
ph->checker = ngx_http_core_post_rewrite_phase;
ph->next = find_config_index;
n++;
ph++;
}
continue;
case NGX_HTTP_ACCESS_PHASE:
checker = ngx_http_core_access_phase;
n++;
break;
case NGX_HTTP_POST_ACCESS_PHASE: // 不允许假如模块自定义的处理方法
if (use_access) {
ph->checker = ngx_http_core_post_access_phase;
ph->next = n;
ph++;
}
continue;
case NGX_HTTP_TRY_FILES_PHASE: // 不允许加入模块自定义的处理方法
if (cmcf->try_files) {
ph->checker = ngx_http_core_try_files_phase;
n++;
ph++;
}
continue;
case NGX_HTTP_CONTENT_PHASE:
checker = ngx_http_core_content_phase;
break;
default:
checker = ngx_http_core_generic_phase;
}
n += cmcf->phases[i].handlers.nelts;
for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) { //in reversed order
ph->checker = checker;
ph->handler = h[j];
ph->next = n;
ph++;
}
}
这段代码很简单,就是依次将每个阶段的handler放在cmcf->phase_engine.handlers中。
这里讲一下几点。首先可以看到switch语句中对于有些阶段会用break,有些阶段会用continue。用continue表示对于当前阶段不会走到后面的for循环,也就是说,不会将continue对应的阶段的handler函数加入到cmcf->phase_engine.handlers中。换句话说,Nginx不支持往这些阶段中添加handler函数。包括以下四个阶段
1.NGX_HTTP_FIND_CONFIG_PHASE
2.NGX_HTTP_POST_REWRITE_PHASE
3.NGX_HTTP_POST_ACCESS_PHASE
4.NGX_HTTP_TRY_FILES_PHASE
对应地在处理请求时,只执行它的checker函数。
我们知道cmcf->phase_engine.handlers数组每个成员的类型是:
struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker;
ngx_http_handler_pt handler;
ngx_uint_t next;
};
从代码可以看到,next始终指向下一个阶段的第一个handler在cmcf->phase_engine.handlers中的位置。因此next提供了一种忽略当前阶段的其他handler直接进入下一阶段handler处理请求的方式:
r->phase_handler= next;
(注:每个阶段由多个handler组成,同一个阶段的所有handler在cmcf->phase_engine.handlers数组中出于连续的位置上)
而继续用当前阶段的handler处理则:
r->phase_handler++;
r->phase_handler一般是在各个checker函数中被改变的。而checker函数根据handler函数的返回结果改变这个值。
(注:相同阶段的所有handler对应的checker函数是一样的)。
总结
本文介绍了cmcf->phase_engine.handlers的初始化过程。cmcf->phase_engine.handlers是一个一维数组,它将所有阶段的handler存储起来,相同阶段的handler在cmcf->phase_engine.handlers中的位置相连续。在处理http请求时,依次调用cmcf->phase_engine.handlers中的handler(通过对应的checker调用handler),通过handler中的next来实现从一个阶段跳跃到下一个阶段的处理。处理请求时,主要是通过请求的r->phase_handler来标识这个请求当前被cmcf->phase_engine.handlers中的哪个handler处理。
这样实现了http请求的多阶段异步处理。
这里没有详细介绍各个阶段的功能,我们后面再详细介绍各个阶段在处理http请求中的具体作用。