Nginx filter 模块解析
我们知道nginx很多功能都是通过filter模块来实现的,如:替换content的sub module、content压缩的gzip module等。接下去我们看看nginx是怎样处理filter模块的。
Nginx filter module所有的代码都在src\http\module\目录中,打开可以看到nginx拥有十几个filter module,以xxx_filter_module.c结尾的源文件便是filter module。
Http请求分为Header、Content两部分,粗略的看代码可以发现,nginx对Http Header、Content使用了分别的filter函数进行处理。举个例子,我们看gzip filter module的代码可以发现,ngx_http_gzip_header_filter函数便是nginx gzip对Http Header的处理,同样ngx_http_gzip_body_filter函数便是对Http Content的处理了。
nginx调用这十几个filter函数的流程便是本节内容所要讨论的。
在源码配置的过程中,会生成/objs/ngx_modules.c文件,其中定义了一个名为ngx_modules的数组,其中的内容便是nginx所有的module,源码为:
ngx_module_t *ngx_modules[] = {
…………
&ngx_http_proxy_module,
&ngx_http_memcached_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_sub_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};
细心的朋友可能会发现,从ngx_http_write_filter_module开始,下面的全为filter_module结尾,没错,下面以filter_module结尾的便是nginx filter module。
我们首先通过较熟悉的gzip filter module入手,我们先看init函数ngx_http_gzip_filter_init,源码为:
static ngx_int_t
ngx_http_gzip_filter_init(ngx_conf_t *cf)
{
// 将 gzip head filter 串到整个链表的头部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_gzip_header_filter;
// 将 gzip body filter 串到整个链表的头部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_gzip_body_filter;
return NGX_OK;
}
代码其实挺简单的,做了两个链表串接操作。我们先看header,ngx_http_top_header_filter为指向整个链表头结点的函数指针(注意:此处为函数指针),此处将next指针指向头指针,将头指针指向gzip header filter,然后在函数ngx_http_gzip_header_filter处理完成后,最终又会调用ngx_http_next_header_filter,这样就将gzip header filter结点串到链表的前面了。Body 处理过程类似,不予重复。
从如上代码分析可以看出,filter module都是串在链表前面的,所以ngx_modules数组中ngx_http_not_modified_filter_module应该是最后一个被串入的,也就是说,此模块应该在链表的最前面。
到此为止,我们知道nginx所有filter module都是通过链表串接起来的,并且数组中最后面的元素在链表的第一个结点中。接下去我们看看nginx怎样将整个链表执行起来。
ngx_http_core_module.c中ngx_http_send_header便是整个filter链的开始,在其中调用了ngx_http_top_header_filter函数,来启动整个header filter。同样ngx_http_output_filter便是启动整个body filter的函数。当整个head链处理完成之后,再行处理body链。
我们通过nginx head filter中第一个被处理的filter来仔细分析一下。由于是逆向串入的,所以ngx_http_not_modified_filter_module就是第一个被处理的filter了。
我们还是先看init函数ngx_http_not_modified_filter_init,源码为:
static ngx_int_t
ngx_http_not_modified_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
return NGX_OK;
}
非常简单,仅仅将head filter模块串入,此模块没有body filter,此处分析就先略过去了,正好简化分析。
我们再看刚才初始化后的入口函数ngx_http_not_modified_header_filter,源码为:
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
if (r->headers_out.status != NGX_HTTP_OK
|| r != r->main
|| r->headers_out.last_modified_time == -1)
{
return ngx_http_next_header_filter(r);
}
// 判断是否应该返回 Http 412(Precondition Failed)
if (r->headers_in.if_unmodified_since) {
return ngx_http_test_precondition(r);
}
// 判断是否应该返回 Http 304(Not Modified)
if (r->headers_in.if_modified_since) {
return ngx_http_test_not_modified(r);
}
return ngx_http_next_header_filter(r);
}
代码也挺简单,首先判断Http状态、一些Flag等信息,如果没有通过判断规则,则直接跳入下一个head filter。然后判断是否没达到返回条件,即:nginx是否应该返回Http 412(Precondition Failed)状态码。然后判断是否应该返回Http 304(Not Modified)状态码。最终如果都没判断通过的话,则直接跳入下一个head filter的处理了。
我们做个假设r->headers_in.if_modify_since不为NULL,则进入ngx_http_test_not_modified,在此函数中,如果执行成功,则将http状态值设置为304,最终跳到下一个head filter。
回过头来再看看,由于此filter仅仅处理Http head信息,无需处理任何Http Content,所以当然就没有类似 body_filter的函数了。
我们接下去看第二个将要处理的模块,即ngx_modules中的倒数第二项。在处理倒数第二项时,其仅仅对Http Content做了处理,处理的流程类似于前面的head分析,此处就不详细解释了。
最终,我们可以看一下ngx_http_header_filter_module,nginx filter module对Http Header的最后处理。先看init函数ngx_http_header_filter_init,由于这个是第一个结点,在这之前并不存在head filter结点,所以仅仅需要赋值ngx_http_top_header_filter即可。在此head filter中,最终会调用ngx_http_write_filter函数(在ngx_http_header_filter之中)完成整个nginx head filter的处理。
ngx_http_writer_filter_module最终对Http body进行处理,最终会调用send_chain函数(ngx_http_write_filter)完成整个nginx body filter的处理。
到此为止,我们解释了整个filter module链的串接过程,也做出了实例分析。但有个问题一直未曾解释,nginx是怎样调用所有filter module的init函数的,因为查看代码知道,每个filter的init函数名不相同,filter module也仅仅存在于一个c文件中,并无对应的头文件。
本篇开头,我们列出过一个结构体,其中包含了所有nginx module的指针,而对应于ngx_http_not_modified_filter_module的是最后一个,此结构体最终的定义在ngx_http_not_modified_filter_module.c文件中,源码为:
ngx_module_t ngx_http_not_modified_filter_module = {
NGX_MODULE_V1,
&ngx_http_not_modified_filter_module_ctx, /* module context */
NULL, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
为了说明方便,列出 ngx_module_t 的定义:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
可以看到,最终启用的ngx_module_t的数据项为 ngx_module_t[index]::ctx(注意NGX_MODULE_V1是一个宏,对应前面7项),此ctx定义为void*类型,在此filter module中,最终转化为类型ngx_http_module_t,其中的数据为:
typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
static ngx_http_module_t ngx_http_not_modified_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_not_modified_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
至此我们看到了ngx_http_not_modified_filter_init函数,也就是说,这个函数最终可以通过ngx_modules数组来访问并调用,示例代码:
ngx_modules[module index]->ctx->postconfiguration(cf);
如上module index假设为ngx_http_not_modified_filter_module的数组索引,ngx_modules[module index]->ctx便是ngx_http_not_modified_filter_module的ctx,即ngx_http_not_modified_filter_module_ctx。ngx_modules[module index]->ctx的类型为ngx_http_module_t,可以通过预定义类型来进行访问,所以ngx_modules[module index]->ctx->postconfiguration(cf);便调用到了最终的init函数。
在ngx_http.c中有函数ngx_http_block,其中便如上进行调用了,源码片段:
// 循环遍历整个 ngx_modules 数组
for (m = 0; ngx_modules[m]; m++) {
// 对 module 的类型进行判断
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
// 临时时变量 module 指向 ngx_modules[m]->ctx,即 xxx_module_filter_ctx 函数
// 对于 ngx_http_not_modified_filter_module 来说,module 即 ngx_http_not_modified_filter_module_ctx
module = ngx_modules[m]->ctx;
// 判断并调用 postconfiguration 函数,即 xxx_module_filter_init 函数
// 对于 ngx_http_not_modified_filter_module 来说,即调用了 ngx_http_not_modified_filter_init
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}