nginx源代码分析--nginx模块解析

nginx的模块很之多。能够觉得全部代码都是以模块的形式组织。这包含核心模块和功能模块,针对不同的应用场合。并不是全部的功能模块都要被用到,附录A给出的是默认configure(即简单的httpserver应用)下被连接的模块,这里虽说是模块连接。但nginx不会像apache或lighttpd那样在编译时生成so动态库而在程序运行时再进行动态载入。nginx模块源文件会在生成nginx时就直接被编译到其二进制运行文件里。所以假设要选用不同的功能模块。必须对nginx做又一次配置和编译。对于功能模块的选择。假设要改动默认值,须要在进行configure时进行指定,比方新增http_flv功能模块(默认是没有这个功能的,各个选项的默认值能够在文件auto/options内看到)

    [root@localhost nginx-1.2.0]# ./configure --with-http_flv_module

运行后。生成的objs/ngx_modules.c文件内就包括有对ngx_http_flv_module模块的引用了,要再去掉http_flv功能模块。则须要又一次configure,即不带--with-http_flv_module配置后再编译生成新的nginx运行程序。通过运行./configure –help。我们能够看到很多其它的配置选项。

   尽管nginx的模块的非常多。而且某个模块的功能各不同样,可是能够依据功能特性,我们大致能够分为四类:

   1) handlers : 处理client请求并产生响应内容,比方ngx_http_static_moudle模块。负责client的静态页面请求。并将相应的静态磁盘文件作为响应内容输出.

   2)filters : 对handlers产生的响应内容做各种过滤处理(即增。删,改),比方 ngx_http_not_modify_filter_moudle,假设通过时间推断前后2次请求的响应内容没有发生不论什么改变,那么能够直接响应"304 Not Modified"状态标识,让client使用缓存就可以,而原本发送的响应内容将被清除掉.

  3)upstream : 假设存在后端真实的server,nginx 能够利用upstream模块充当反向代理的角色,对client的请求仅仅负责转发到后端的真实server,如ngx_http_proxy_moudle模块.

  4)load-balance : 在nginx充其中间代理时,因为后端真实server往往多于一个,对于某一次client的请求,怎样选择相应的后端真实server来进行处理。这就有类似于ngx_http_upstream_ip_hash_module这种模块来实现不同的负载均衡算法(Load Balance)。

在此,我们先来了解一些数据结构:

复制代码
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_s中值得注意的几个字段 ctx , commands, type,当中commands字段表示当前模块的能够解析的配置项目。表示模块类型的type值仅仅有5种,而同一类型的模块的ctx数据类型都是同样的。

序号

type值

ctx指向数据类型

1

NGX_CORE_MODULE

ngx_core_module_t

2

NGX_EVENT_MODULE

ngx_event_module_t

3

NGX_CONF_MODULE

NULL

4

NGX_HTTP_MODULE

ngx_http_module_t

5

NGX_MAIL_MODULE

ngx_mail_module_t

复制代码
typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
............................
typedef struct {
    ngx_str_t              *name;

    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    ngx_event_actions_t     actions;
} ngx_event_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;
复制代码

    上面表中的第3列的数据类型比較重要。它的字段基本上都是一些回调函数,这些回调函数会在其模块相应的配置文件解析过程 前/中/后 会适时被调用,做一些内存准备。初始化,配置值检查,配置值填充。合并,回调函数挂载等初始工作.

    以下我们以ngx_http_core_moudle模块为例,type 为 NGX_HTTP_MOUDLE, ctx 指向ngx_http_moudle_t结构体变量ngx_http_core_module_ctx.

复制代码
static ngx_http_module_t  ngx_http_core_module_ctx = {
    ngx_http_core_preconfiguration,        /* preconfiguration */
    NULL,                                  /* postconfiguration */

    ngx_http_core_create_main_conf,        /* create main configuration */
    ngx_http_core_init_main_conf,          /* init main configuration */

    ngx_http_core_create_srv_conf,         /* create server configuration */
    ngx_http_core_merge_srv_conf,          /* merge server configuration */

    ngx_http_core_create_loc_conf,         /* create location configuration */
    ngx_http_core_merge_loc_conf           /* merge location configuration */
};
复制代码

     依据上面的代码。我们能够非常明显看到各个回调函数的回调时机,比如:ngx_http_core_preconfiguration将在进行http块配置解析前被调用。所以内在ngx_http_block()函数里看到这种代码:

复制代码
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
         for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
}
复制代码

至于这些回调函数内的详细逻辑,如前所述通常是一些初始或默认值填充工作,但也有回调函数挂载的设置。比方ngx_http_static_module模块的postconfiguration字段回调函数ngx_http_static_init()就是将自己的处理函数ngx_http_static_handler()挂载在http处理状态机上,但整体来看这毕竟都仅仅是一些简单的初始准备工作.

   handlers模块

      对于clienthttp请求过程。为了获得更强的控制力,nginx将其细分成多个阶段处理,每一个阶段都有零个或多个回调函数专门处理,当我们编写handler模块的时候,必须把模块功能挂载在正确的阶段点。如前面所描写叙述的ngx_http_static_moudle 将自己的功能模块处理函数ngx_http_static_handler()挂载在NGX_HTTP_CONTENT_PHASE阶段.

     Http请求处理过程一共分为11个阶段,每个阶段相应的处理功能都比較单一,这样能尽量让nginx模块代码更为内聚:

    

序号

阶段宏名

阶段描写叙述

0

NGX_HTTP_POST_READ_PHASE

读取请求内容阶段

1

NGX_HTTP_SERVER_REWRITE_PHASE

Server请求地址重写阶段

2

NGX_HTTP_FIND_CONFIG_PHASE

配置查找阶段

3

NGX_HTTP_REWRITE_PHASE

Location请求地址重写阶段

4

NGX_HTTP_POST_REWRITE_PHASE

请求地址重写提交阶段

5

NGX_HTTP_PREACCESS_PHASE

訪问权限检查准备阶段

6

NGX_HTTP_ACCESS_PHASE

訪问权限检查阶段

7

NGX_HTTP_POST_ACCESS_PHASE

訪问权限检查提交阶段

8

NGX_HTTP_TRY_FILES_PHASE

配置项try_files处理阶段 

9

NGX_HTTP_CONTENT_PHASE

内容产生阶段

10

NGX_HTTP_LOG_PHASE

日志模块处理阶段

    并不是某个阶段都能挂载自己定义的回调函数,比方NGX_HTTP_TRY_FILE_PHASE阶段就是针对配置项try_files的特定处理阶段段。NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_ACCESS_PHASE与NGX_HTTP_POST_REWRITE_PHASE这三个阶段也是为了完毕nginx特定的功能,就算给这几个阶段加上回调函数。也永远不会被调用。我们的自己定义模块回调函数挂载在NGX_HTTP_CONTENT_PHASE阶段的情况比較多,毕竟大部分情况下的业务需求是改动HTTP响应数据。nginx自身的产生响应内容的模块。像ngx_http_static_module、ngx_http_random_index_module、ngx_http_index_module、ngx_http_gzip_static_module、ngx_http_dav_module等都是挂载在这个阶段。

   大多数情况下,功能模块会在其相应配置解析完后的回调函数,也就是ngx_http_moudle_t结构体的postconfiguration字段指向的函数内将当前模块的回调功能函数挂载到这11个阶段当中一个上.

以ngx_http_static_module为例:

复制代码
ngx_http_module_t  ngx_http_static_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_static_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 */
};

static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_static_handler;  

    return NGX_OK;
复制代码

    在模块ngx_http_static_module的postconfiguration回调函数ngx_http_static_init()内。将ngx_http_static_module模块的核心功能函数ngx_http_static_handler()挂载在Http请求处理流程中的NGX_HTTP_CONTENT_PHASE阶段。

这样。当一个client的http静态页面请求发送到nginxserver,nginx就行调用到我们这里注冊的ngx_http_static_handler()函数,

   

各个功能模块将其自身的功能函数挂载在cmcf->phases后,内部的情况例如以下图所看到的: 

回调函数会依据模块的不同而不同.这些回调函数的调用都时有条件的,调用后也要做一些依据返回值的结果处理.比方某次处理是否进入到阶段NGX_HTTP_CONTENT_PARSE的回调函数的处理,这须要一个事前推断.所以在函数ngx_http_init_phase_handlers()里对全部这个回调函数进行一次重组.

struct ngx_http_phase_handler_s {
      ngx_http_phase_handler_pt checker;  //阶段检查函数
      ngx_http_handler_pt handler;
      ngx_uint_t next;
};

     但从上图中能够看到,该函数仅仅把有回调函数的处理阶段给提取了出来,同一时候利用ngx_http_phase_handler_t结构体数组对这些回调函数进行重组,不仅加上了进入回调函数的条件推断checker函数,并且通过next字段的使用,把原本的二维数组实现转化为可直接在一维函数数组内部跳动;一般来讲,二维数组的遍历须要两层循环。而遍历一维函数数组就仅仅需一层循环。

     再来看对http请求进行分段处理的核心函数ngx_http_core_run_phase:

复制代码
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_core_run_phases函数中r->phase_handler标志当前处理的序号。对于一个client的最開始的请求的时刻。 该值当然就是0了,while循环推断假设存在checker函数(末尾数组元素的checker函数为null),那就调用该checker函数并有可能调用对应的回调函数,以NGX_HTTP_ACCESS_PHASE阶段的ngx_http_core_access_phase()函数为例:
复制代码
ngx_int_t
ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
        if (r != r->main) {
            r->phase_handler = ph->next;
            return NGX_AGAIN;
        }
        
         rc = ph->handler(r);

        if (rc == NGX_DECLINED) {
             r->phase_handler++;
             return NGX_AGAIN;
        }

        if (rc == NGX_AGAIN || rc == NGX_DONE) {
             return NGX_OK;
        } 
        ngx_http_finalize_request(r, rc);
        return NGX_OK;
}
复制代码

能够看到,一个功能模块的handler函数能够返回多种类型的值,而且这些值有其固有的含义:

序号

返回值

含义

1

NGX_OK

当前阶段已经被成功处理,必须进入到下一个阶段

2

NGX_DECLINED

当前回调不处理当前情况,进入到下一个回调处理

3

NGX_AGAIN

当前处理所需资源不足,须要等待所依赖事件发生

4

NGX_DONE

当前处理结束,仍需等待进一步事件发生后做处理

5

NGX_ERROR, NGX_HTTP_…

当前回调处理错误发生,须要进入到异常处理流程



Filter模块:
对于http请求处理handlers产生的响应内容,在输出client之前须要做过滤处理,这些过滤处理对于完整功能的增强实现和性能的提升是很有必要的,比方过滤模块ngx_http_chunked_filter_moudle,那么就无法完整支持http中chunk的功能。

假设没有ngx_http_not_modified_filter_module过滤模块。那么就无法让client使用本地缓存来提高性能;诸如这些都须要过滤模块的支持。因为响应数据包含响应头和响应体,所以以此相应,任一filter模块必须提供处理响应头的header过滤函数(比方ngx_http_not_modified_filter_module模块提供的ngx_http_not_modified_header_filter()函数)或处理响应体的body过滤功能函数(比方ngx_http_copy_filter_module模块提供的ngx_http_copy_filter()函数)或两者皆有(比方ngx_http_chunked_filter_module模块提供的ngx_http_chunked_header_filter()函数和ngx_http_chunked_body_filter()函数)。

  全部的header过滤功能函数和body过滤功能函数会分别组成各自的两条过滤链。例如以下图所看到的(使用附录A所列模块):

     这2条过滤链怎么形成的呢?在源文件ngx_http.c中,能够看到有2个函数指针变量:

   ngx_int_t  (*ngx_http_top_header_filter) (ngx_http_request_t *r);

   ngx_int_t  (*ngx_http_top_body_filter) (ngx_http_request_t *r, ngx_chain_t *ch);

    这是整个nginx范围内可见的全局变量。然后在每个filter模块内,我们还会看到类似于这种定义(假设当前模块仅仅有header过滤功能函数或仅仅有body过滤功能函数。那么例如以下定义也就仅仅有对应的那个变量):

         static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;

         static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

        注意到static修饰符,也就是说这两个变量是属于模块范围内可见的局部变量。

有了这些函数指针变量,再在各个filter模块的postconfiguration回调函数(该函数会在其相应配置解析完后被调用做一些设置工作,前面已经描写叙述过)内。全局变量与局部变量的巧妙赋值使得终于行成了两条过滤链。以header过滤链为例,通过附录A的模块列表ngx_modules变量。能够看到ngx_http_header_filter_module是具有header过滤功能函数的序号最小的过滤模块,其postconfiguration回调函数例如以下:

复制代码
618:    ngx_http_header_filter_init(ngx_conf_t *cf)
619:    {
620:        ngx_http_top_header_filter = ngx_http_header_filter;
621:    
622:        return NGX_OK;
623:    }
复制代码
复制代码
232:    static ngx_int_t
233:    ngx_http_chunked_filter_init(ngx_conf_t *cf)
234:    {
235:        ngx_http_next_header_filter  = ngx_http_top_header_filter;
236:        ngx_http_top_header_filter   = ngx_http_chunked_header_filter;
        }
复制代码

        其他过滤模块的类此增加,逐步形成终于的完整header过滤链;当然。body过滤链的形成过程也与此类似。两条过滤链形成后。其相应的调用入口分别在函数ngx_http_send_header()和函数ngx_http_output_filter()内:

复制代码
1889:    ngx_int_t
1890:    ngx_http_send_header(ngx_http_request_t *r)
1891:    {
1892:    …
1897:        return ngx_http_top_header_filter(r);
1898:    }
1899:    
1901:    ngx_int_t
1902:    ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
1903:    {
1904:    …
1912:        rc = ngx_http_top_body_filter(r, in);
1913:    …
1919:        return rc;
1920:    }
复制代码

      这两个函数很easy。主要是通过过滤链的链头函数指针全局变量进入到两条过滤链内,进而依次运行链上的各个函数。

比方这里ngx_http_top_header_filter指向的是ngx_http_not_modified_header_filter()函数,因此进入到该函数内运行。而在该函数的运行过程中又会依据情况。继续通过当前模块内的函数指针局部变量ngx_http_next_header_filter间接的调用到header过滤链的下一个过滤函数,这对保证过滤链的前后承接是很必要的。除非我们遇到无法继续处理的错误。此时仅仅有返回NGX_ERROR这种值:   

复制代码
52:    static ngx_int_t
53:    ngx_http_not_modified_header_filter(ngx_http_request_t *r)
54:    {
55:    …
70:        return ngx_http_next_header_filter(r);
71:    }
复制代码

        依据HTTP协议具备的响应头影响或决定响应体内容的特点,所以通常是先对响应头进行过滤,依据头过滤处理返回值再对响应体进行过滤处理。假设在响应头过滤处理中出错或某些特定情况下,响应体过滤处理能够不用再进行。

        

原文地址:https://www.cnblogs.com/liguangsunls/p/7286025.html