nginx http模块开发(1) - 基础

2018-07-19 21:28:33

模块化架构是 nginx 的核心,整个 nginx 就是由各种 core、event、http、stream等核心模块搭建起来的,也使得我们可以自己开发相应的子模块嵌入到里面来完成特定的任务。平常开发的模块以 http 模块为主,所以接下来的讲解以开发 http 模块为目标进行。

nginx的模块使用 ngx_module_t 结构体描述,它的成员很多,但有些只是保留字段,不需要关心。

typedef struct ngx_module_s          ngx_module_t;

struct ngx_module_s {
    ngx_uint_t            ctx_index; //在相同type类里的模块序号

    ngx_uint_t            index;   //所有模块数组里的序号

    char                 *name;  //模块的名字

    // 两个保留字段,1.9之前有4个
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    //存版本号 1014000
    ngx_uint_t            version;

    // 模块签名信息
    const char           *signature;

    // 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数
    // core模块的ctx
    //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;
    void                 *ctx;

    // 模块支持的指令,数组形式,最后用空对象表示结束
    ngx_command_t        *commands;

    // 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等
    ngx_uint_t            type;

    // 以下7个函数会在进程的启动或结束阶段被调用

    // init_master目前nginx不会调用
    ngx_int_t           (*init_master)(ngx_log_t *log);

    // 在ngx_init_cycle里被调用
    // 在master进程里,fork出worker子进程之前
    // 做一些基本的初始化工作,数据会被子进程复制
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    // 在ngx_single_process_cycle/ngx_worker_process_init里调用
    // 在worker进程进入工作循环之前被调用
    // 初始化每个子进程自己专用的数据
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);

    // init_thread目前nginx不会调用
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);

    // exit_thread目前nginx不会调用
    void                (*exit_thread)(ngx_cycle_t *cycle);

    // 在ngx_worker_process_exit调用
    void                (*exit_process)(ngx_cycle_t *cycle);

    // 在ngx_master_process_exit(os/unix/ngx_process_cycle.c)里调用
    void                (*exit_master)(ngx_cycle_t *cycle);

    // 下面8个成员通常用用NGX_MODULE_V1_PADDING填充
    // 暂时无任何用处
    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;
};


模块签名

早期的 nginx 只支持静态模块,模块都是以静态链接的方式与 nginx 框架代码集成在可执行文件里。在1.9.11 版本之后,nginx 引入了动态模块,允许把模块编译成动态用指令 load_moudle 在运行时动态加载。 

动态模块和 nginx 主程序可以分离编译,各自独立发布,虽然增加了 nginx 的灵活性, 也带来了新的问题。其中一个关键的问题就是要保证模块的兼容性。

nginx 对此给出的答案是使用 "签名",依据操作系统和编译环境的各种特征生成一个 "签名" 字符串,作为模块的 "特征码",只有符合 "签名" 的模块才能被主程序加载。

#define NGX_MODULE_SIGNATURE                                                  \
    NGX_MODULE_SIGNATURE_0 NGX_MODULE_SIGNATURE_1 NGX_MODULE_SIGNATURE_2      \
    NGX_MODULE_SIGNATURE_3 NGX_MODULE_SIGNATURE_4 NGX_MODULE_SIGNATURE_5      \
    NGX_MODULE_SIGNATURE_6 NGX_MODULE_SIGNATURE_7 NGX_MODULE_SIGNATURE_8      \
    NGX_MODULE_SIGNATURE_9 NGX_MODULE_SIGNATURE_10 NGX_MODULE_SIGNATURE_11    \
    NGX_MODULE_SIGNATURE_12 NGX_MODULE_SIGNATURE_13 NGX_MODULE_SIGNATURE_14   \
    NGX_MODULE_SIGNATURE_15 NGX_MODULE_SIGNATURE_16 NGX_MODULE_SIGNATURE_17   \
    NGX_MODULE_SIGNATURE_18 NGX_MODULE_SIGNATURE_19 NGX_MODULE_SIGNATURE_20   \
    NGX_MODULE_SIGNATURE_21 NGX_MODULE_SIGNATURE_22 NGX_MODULE_SIGNATURE_23   \
    NGX_MODULE_SIGNATURE_24 NGX_MODULE_SIGNATURE_25 NGX_MODULE_SIGNATURE_26   \
    NGX_MODULE_SIGNATURE_27 NGX_MODULE_SIGNATURE_28 NGX_MODULE_SIGNATURE_29   \
    NGX_MODULE_SIGNATURE_30 NGX_MODULE_SIGNATURE_31 NGX_MODULE_SIGNATURE_32   \
    NGX_MODULE_SIGNATURE_33 NGX_MODULE_SIGNATURE_34

#define NGX_MODULE_SIGNATURE_0                                                \
    ngx_value(NGX_PTR_SIZE) ","                                               \
    ngx_value(NGX_SIG_ATOMIC_T_SIZE) ","                                      \
    ngx_value(NGX_TIME_T_SIZE) ","

#if (NGX_HAVE_KQUEUE)
#define NGX_MODULE_SIGNATURE_1   "1"
#else
#define NGX_MODULE_SIGNATURE_1   "0"
#endif

#if (NGX_HAVE_IOCP)
#define NGX_MODULE_SIGNATURE_2   "1"
#else
#define NGX_MODULE_SIGNATURE_2   "0"
#endif

#if (NGX_HAVE_FILE_AIO || NGX_COMPAT)
#define NGX_MODULE_SIGNATURE_3   "1"
#else
#define NGX_MODULE_SIGNATURE_3   "0"
#endif

#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT)
#define NGX_MODULE_SIGNATURE_4   "1"
#else
#define NGX_MODULE_SIGNATURE_4   "0"
#endif

#if (NGX_HAVE_EVENTFD)
#define NGX_MODULE_SIGNATURE_5   "1"
#else
#define NGX_MODULE_SIGNATURE_5   "0"
#endif

#if (NGX_HAVE_EPOLL)
#define NGX_MODULE_SIGNATURE_6   "1"
#else
#define NGX_MODULE_SIGNATURE_6   "0"
#endif

#if (NGX_HAVE_KEEPALIVE_TUNABLE)
#define NGX_MODULE_SIGNATURE_7   "1"
#else
#define NGX_MODULE_SIGNATURE_7   "0"
#endif

#if (NGX_HAVE_INET6)
#define NGX_MODULE_SIGNATURE_8   "1"
#else
#define NGX_MODULE_SIGNATURE_8   "0"
#endif

#define NGX_MODULE_SIGNATURE_9   "1"
#define NGX_MODULE_SIGNATURE_10  "1"

#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
#define NGX_MODULE_SIGNATURE_11  "1"
#else
#define NGX_MODULE_SIGNATURE_11  "0"
#endif

#define NGX_MODULE_SIGNATURE_12  "1"

#if (NGX_HAVE_SETFIB)
#define NGX_MODULE_SIGNATURE_13  "1"
#else
#define NGX_MODULE_SIGNATURE_13  "0"
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
#define NGX_MODULE_SIGNATURE_14  "1"
#else
#define NGX_MODULE_SIGNATURE_14  "0"
#endif

#if (NGX_HAVE_UNIX_DOMAIN)
#define NGX_MODULE_SIGNATURE_15  "1"
#else
#define NGX_MODULE_SIGNATURE_15  "0"
#endif

#if (NGX_HAVE_VARIADIC_MACROS)
#define NGX_MODULE_SIGNATURE_16  "1"
#else
#define NGX_MODULE_SIGNATURE_16  "0"
#endif

#define NGX_MODULE_SIGNATURE_17  "0"
#define NGX_MODULE_SIGNATURE_18  "0"

#if (NGX_HAVE_OPENAT)
#define NGX_MODULE_SIGNATURE_19  "1"
#else
#define NGX_MODULE_SIGNATURE_19  "0"
#endif

#if (NGX_HAVE_ATOMIC_OPS)
#define NGX_MODULE_SIGNATURE_20  "1"
#else
#define NGX_MODULE_SIGNATURE_20  "0"
#endif

#if (NGX_HAVE_POSIX_SEM)
#define NGX_MODULE_SIGNATURE_21  "1"
#else
#define NGX_MODULE_SIGNATURE_21  "0"
#endif

#if (NGX_THREADS || NGX_COMPAT)
#define NGX_MODULE_SIGNATURE_22  "1"
#else
#define NGX_MODULE_SIGNATURE_22  "0"
#endif

#if (NGX_PCRE)
#define NGX_MODULE_SIGNATURE_23  "1"
#else
#define NGX_MODULE_SIGNATURE_23  "0"
#endif

#if (NGX_HTTP_SSL || NGX_COMPAT)
#define NGX_MODULE_SIGNATURE_24  "1"
#else
#define NGX_MODULE_SIGNATURE_24  "0"
#endif

#define NGX_MODULE_SIGNATURE_25  "1"

#if (NGX_HTTP_GZIP)
#define NGX_MODULE_SIGNATURE_26  "1"
#else
#define NGX_MODULE_SIGNATURE_26  "0"
#endif

#define NGX_MODULE_SIGNATURE_27  "1"

#if (NGX_HTTP_X_FORWARDED_FOR)
#define NGX_MODULE_SIGNATURE_28  "1"
#else
#define NGX_MODULE_SIGNATURE_28  "0"
#endif

#if (NGX_HTTP_REALIP)
#define NGX_MODULE_SIGNATURE_29  "1"
#else
#define NGX_MODULE_SIGNATURE_29  "0"
#endif

#if (NGX_HTTP_HEADERS)
#define NGX_MODULE_SIGNATURE_30  "1"
#else
#define NGX_MODULE_SIGNATURE_30  "0"
#endif

#if (NGX_HTTP_DAV)
#define NGX_MODULE_SIGNATURE_31  "1"
#else
#define NGX_MODULE_SIGNATURE_31  "0"
#endif

#if (NGX_HTTP_CACHE)
#define NGX_MODULE_SIGNATURE_32  "1"
#else
#define NGX_MODULE_SIGNATURE_32  "0"
#endif

#if (NGX_HTTP_UPSTREAM_ZONE)
#define NGX_MODULE_SIGNATURE_33  "1"
#else
#define NGX_MODULE_SIGNATURE_33  "0"
#endif

// 1.11.x新增,标记与nginx plus的兼容性
#if (NGX_COMPAT)
#define NGX_MODULE_SIGNATURE_34  "1"
#else
#define NGX_MODULE_SIGNATURE_34  "0"
#endif

从上面的定义可以看出,最终得到的宏 NGX_MODULE_SIGNATURE 将会是一个很长的静态字符串。

模块的种类

nginx 框架定义了6种类型的模块,分别是 core、conf、event、stream、http、mail。目前主要开发的是 http 模块,只要特别关注 NGX_CORE_MODULE 和 NGX_HTTP_MODULE。

#define NGX_HTTP_MODULE      0x50545448
#define NGX_EVENT_MODULE     0x544E5645
#define NGX_CORE_MODULE      0x45524F43
#define NGX_CONF_MODULE      0x464E4F43
#define NGX_MAIL_MODULE      0x4C49414D
#define NGX_STREAM_MODULE    0x4d525453

函数指针表

ngx_module_t 里的 ctx 类型是 void *,意味着它的内容是不确定的,必须配合 type 才能确定 ctx 具体含义。

nginx 的6类模块各自定义了 ctx 结构,名字为 ngx_xxx_module_t。

core 模块的ctx,该结构比较简单,只有两个函数函数指针,用于创建和初始化配置结构。core模块通常不处理业务,只负责构建子系统。

//ngx_module.h

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;

nginx一共只有为数不多的几个核心模块,包括 ngx_core_module、ngx_errlog_module、ngx_events_module、ngx_http_module、ngx_stream_module等,它们都是 nginx 底层最核心的模块,nginx 框架直接与这些模块交互。

对于 ngx_http_module,它只负责把所有的 http 模块管理组织起来,接入 nginx 框架,而真正的业务处理则放到 http 模块 ngx_http_core_module 里。

http模块的ctx定义如下:

//ngx_http_config.h

typedef struct {
    // ngx_http_block里,创建配置结构体后,开始解析之前调用
    // 常用于添加变量定义
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);

    // ngx_http_block里,解析、合并完配置后调用
    // 常用于初始化模块的phases handler
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    // 创建模块的main配置,只有一个,在http main域
    void       *(*create_main_conf)(ngx_conf_t *cf);

    // 初始化模块的main配置,只有一个,在http main域
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    // 创建、合并模块的srv配置
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    // 创建、合并模块的location配置
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

模块的组织形式

nginx 在调用 configure 时候会在objs目录里生成  ngx_modules.c文件。里面包含了所有静态模块的定义。

extern ngx_module_t  ngx_core_module;
...
extern ngx_module_t  ngx_http_module;
extern ngx_module_t  ngx_http_core_module;
extern ngx_module_t  ngx_http_log_module;
...
extern ngx_module_t  ngx_http_not_modified_filter_module;
ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    ...
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
    ...
    &ngx_http_not_modified_filter_module,
    NULL
};
char *ngx_module_names[] = {
    "ngx_core_module",
    ...
    "ngx_http_module",
    "ngx_http_core_module",
    "ngx_http_log_module",
    ...
    "ngx_http_not_modified_filter_module",
    NULL
};

上面只是简单利用 extern 关键字包模块包含进来。实际模块定义在各自的模块文件里。

模块初始化

//ngx_module.c

ngx_int_t
ngx_preinit_modules(void)
{
    ngx_uint_t  i;

    // 从0开始,为所有静态模块设置序号和名字
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = i;
        ngx_modules[i]->name = ngx_module_names[i];
    }

    // ngx_modules_n保存了静态模块数量
    ngx_modules_n = i;

    // ngx_max_module是模块数量的上限,为静态模块数量+128
    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;

    return NGX_OK;
}

ngx_cycle_t 是nginx框架的核心数据结构,整个生命周期都存在,通过初始化函数,会把上面的模块定义复制到 ngx_cycle_t 结构体内部。

通过下面函数的拷贝,ngx_modules、ngx_max_module、ngx_modules_n 三个全局变量的使命就完成了,后续不再使用。

//ngx_module.c

ngx_int_t
ngx_cycle_modules(ngx_cycle_t *cycle){
    cycle->modules = ngx_pcalloc(cycle->pool, (ngx_max_module + 1)
                                              * sizeof(ngx_module_t *));
    if (cycle->modules == NULL) {
        return NGX_ERROR;
    }

    // 拷贝之后ngx_modules数组不再使用
    ngx_memcpy(cycle->modules, ngx_modules,
               ngx_modules_n * sizeof(ngx_module_t *));

    // 存静态模块数量
    cycle->modules_n = ngx_modules_n;

    return NGX_OK;
}

ngx_module_t 的 index 成员标记了模块在 ngx_modules 数组里的索引位置(0 - ngx_modules_n-1)。

而 ctx_index 是在同类模块里的索引位置。


配置解析

nginx 的模块化架构是跟配置文件紧密结合在一起的,模块决定了配置文件的结构和指令,配置文件用这些指令来调整模块的行为,想要掌握 nginx 的模块架构和运行机制,就必须深刻理解 nginx 的配置解析功能。

ngx_cycle_t

struct ngx_cycle_s {

    // 存储所有模块的配置结构体,是个二维数组
    // 0 = ngx_core_module
    // 1 = ngx_errlog_module
    // 3 = ngx_event_module
    // 4 = ngx_event_core_module
    // 5 = ngx_epoll_module
    // 7 = ngx_http_module
    void                  ****conf_ctx;
    
    ...

    // 1.10,保存模块数组,可以加载动态模块
    // 可以容纳所有模块,大小是ngx_max_module + 1
    ngx_module_t            **modules;

    // 模块数量
    ngx_uint_t                modules_n;

    // 标志位,cycle已经完成模块的初始化,不能再添加模块
    // 在ngx_load_module里检查,不允许加载动态模块
    ngx_uint_t                modules_used;    /* unsigned  modules_used:1; */

    ...
};

conf_ctx 是存储所有模块配置的地方,它初始化分配的内存大小就是 最大模块数*指针大小。

cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));

nginx 通过这种空指针方式可以支持不同类型的模块配置数据,对于核心模块来说直接指向配置数据,而对于其他模块来说,指针指向的可以是另外一个数组,形成一个树形结构。

模块解析是从 ngx_conf_parse 开始读取配置指令,读取的指令信息会先存在 ngx_conf_t 结构体里。

struct ngx_conf_s {
    //保存解析到的指令字符串,0是指令名,其他的是参数
    ngx_array_t          *args;

    // 当前配置的cycle结构体,用于添加监听端口
    ngx_cycle_t          *cycle;

    // 当前环境,指向的是 ngx_cycle_s 的 conf_ctx
    void                 *ctx;


    // 解析配置文件当前的命令类型,解析时检查
    ngx_uint_t            cmd_type;

    ...
};

然后调用 rc = ngx_conf_handler(cf, rc) 来遍历模块查找哪个模块配置了该指令。

我们还是以 ngx_http_module 核心模块为例,它的命令定义如下。

static ngx_command_t  ngx_http_commands[] = {
    { ngx_string("http"),

      // 出现在main域,配置块,无参数
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,

      // 解析http{}配置块,里面有server{}/location{}等
      ngx_http_block,
      0,
      0,
      NULL },

      ngx_null_command
};

可以看到 http 核心模块定义了 http 指令,所以当我们在配置文件解析到 http{},就会通过下面的遍历找到 ngx_http_module  模块。

//ngx_conf_handler

for ( /* void */ ; cmd->name.len; cmd++) {
    ...
            // 上面只是执行指令的检查,来确定是否匹配
            //假设匹配成功
            conf = NULL;

            // NGX_DIRECT_CONF,直接存储在cf->ctx数组里
            // 这个通常是core模块
            if (cmd->type & NGX_DIRECT_CONF) {
                conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];

            //http核心模块在此类型匹配成功
            } else if (cmd->type & NGX_MAIN_CONF) {
                conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);

            // 大部分普通模块不会使用NGX_DIRECT_CONF、NGX_MAIN_CONF
            } else if (cf->ctx) {

                // 对于http/stream模块有意义,其他模块无用
                // cf->ctx是有三个数组的结构体,用cmd->conf偏移量得到数组位置
                confp = *(void **) ((char *) cf->ctx + cmd->conf);

                if (confp) {
                    // 得到在main_conf/srv_conf/loc_conf数组里的模块对应配置结构体
                    // 注意使用的是ctx_index
                    conf = confp[cf->cycle->modules[i]->ctx_index];
                }
            }

            // 调用指令数组里的函数解析,此时的conf指向正确的存储位置
            // cf->args里存储的是指令参数,正确性已经验证过了
            rv = cmd->set(cf, cmd, conf);

            ...
        }
    }   // for结束,检查完所有模块

上面的 conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]); 会返回空指针,因为 ngx_http_module 核心模块的 ctx 没有定义 create_conf 函数。

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;
//ngx_http_module
static ngx_core_module_t  ngx_http_module_ctx = {
    ngx_string("http"),
    NULL,
    NULL
};

下面只会初始化定义 ngx_core_module_t 里定义了 create_conf 函数指针的核心模块。

// 遍历 core 模块并调用 create_conf 创建配置结构体
    for (i = 0; cycle->modules[i]; i++) {
        // 检查type,只处理core模块,数量很少
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }

        //获取core模块的函数表
        module = cycle->modules[i]->ctx;

        // 由于 ngx_http_module 核心模块没有定义 create_conf
        //到此结束
        if (module->create_conf) {
            rv = module->create_conf(cycle);
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            // 这里就不会初始化
            cycle->conf_ctx[cycle->modules[i]->index] = rv;
        }
    }

最后开始调用模块指令配置结构体里的set函数,在 ngx_http_module 核心模块里即为 ngx_http_block


 备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/ca


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。