Nginx filter 模块解析
Nginx filter 模块解析
mickelfeng 发表于5年前
Nginx filter 模块解析
  • 发表于 5年前
  • 阅读 138
  • 收藏 4
  • 点赞 0
  • 评论 0

我们知道nginx很多功能都是通过filter模块来实现的,如:替换contentsub modulecontent压缩的gzip module等。接下去我们看看nginx是怎样处理filter模块的。

Nginx filter module所有的代码都在src\http\module\目录中,打开可以看到nginx拥有十几个filter module,以xxx_filter_module.c结尾的源文件便是filter module

Http请求分为HeaderContent两部分,粗略的看代码可以发现,nginxHttp HeaderContent使用了分别的filter函数进行处理。举个例子,我们看gzip filter module的代码可以发现,ngx_http_gzip_header_filter函数便是nginx gzipHttp 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;

}

    代码其实挺简单的,做了两个链表串接操作。我们先看headerngx_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.cngx_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 412Precondition Failed

    if (r->headers_in.if_unmodified_since) {

        return ngx_http_test_precondition(r);

    }

 

    // 判断是否应该返回 Http 304Not 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 412Precondition Failed)状态码。然后判断是否应该返回Http 304Not 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_modulenginx filter moduleHttp 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 moduleinit函数的,因为查看代码知道,每个filterinit函数名不相同,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_modulectx,即ngx_http_not_modified_filter_module_ctxngx_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;

            }

        }

    }

共有 人打赏支持
mickelfeng
粉丝 213
博文 894
码字总数 503233
×
mickelfeng
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: