Nginx:指令工作原理浅析

模块和指令如何集成到 NGX

对于一个自定义模块,要了解工作原理,则先了解组成。只需要简单过一遍这几个结构:

  • ngx_command_t

  • ngx_http_module_t

  • ngx_module_s

ngx_command_t

首先,模块有自己的配置信息。命名方式为 ngx_http_<module name>_(main|srv|loc)_conf_t

然后,会通过一个静态数组定义指令。(ngx_null_command 标记结尾)

 1static ngx_command_t ngx_http_hello_commands[] = {
 2   {
 3    // ngx_str_t             name;    命令名称
 4        ngx_string("hello_string"),
 5    // ngx_uint_t            type;    命令类型。这里表示是一个能出现在 location 块的配置项,且支持 0 或 1 个参数
 6        NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
 7    // char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);  这里是指定命令的处理函数指针。当解析到这个配置项时,会调用这个函数
 8        ngx_http_hello_string,
 9    // ngx_uint_t            conf;        当前配置项存储的内存位置。有三个内存池:main,srv,loc。这里表示存储在 loc 内存池中
10        NGX_HTTP_LOC_CONF_OFFSET,
11    // ngx_uint_t            offset;      指定该配置项值的精确存放位置。这里表示存储在 ngx_http_hello_loc_conf_t 结构体中的 hello_string 成员中
12        offsetof(ngx_http_hello_loc_conf_t, hello_string),
13    // void                 *post;        不知道,不用管,用到再说。
14        NULL },
15
16    ngx_null_command
17};

ngx_http_module_t

每个模块都有一个 ngx_http_module_t 类型的结构体变量,用于提供一组回调指针。这样当遇到一些情况,就能通知到我们自己的模块。包括这些:

  • preconfiguration

  • postconfiguration

  • create main configuration

  • init main configuration

  • create server configuration

  • merge server configuration

  • create location configuration

  • merge location configuration

如果不想处理,则留 NULL。上面之所以会有 merge 的情况,是考虑到一个符号可能定义在外层,也可能定义在内层。那么采用外层和内层就是一个问题,模块可自行决断。

ngx_module_s

每个模块都有一个 ngx_module_t 类型的变量。这个变量是 Nginx 模块的核心。它包含了模块的基本信息,如模块名称、版本、作者、上下文等等。

其构成如下:

 1struct ngx_module_s {
 2    ngx_uint_t            ctx_index;  index *name  spare0;  spare1  version; *signature;
 3    // -----------上面这些看都不看,直接写 NGX_MODULE_V1 ------------------
 4    void                 *ctx; // 指向静态的 ngx_http_module_t 结构体
 5    ngx_command_t        *commands; // 指向静态的 ngx_http_hello_commands 数组
 6    ngx_uint_t            type; // 一般是 NGX_HTTP_MODULE
 7
 8    ngx_int_t           (*init_master)(ngx_log_t *log);
 9
10    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
11
12    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
13    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
14    void                (*exit_thread)(ngx_cycle_t *cycle);
15    void                (*exit_process)(ngx_cycle_t *cycle);
16
17    void                (*exit_master)(ngx_cycle_t *cycle);
18
19    // uintptr_t             spare_hook0~7 这里填个 NGX_MODULE_V1_PADDING 就完事儿了
20};

type 这里还可能是 NGX_CORE_MODULE、NGX_CONF_MODULE 或者自定义的类型。再往后是一些钩子。这些钩子在 Nginx 启动、关闭、重启等过程中会被调用。log、thread 的钩子目前 Nginx 尚未实现。

那么我们的 handler 是怎么介入到 NGX 的处理流程中呢?一种方法是在 postconfiguration 阶段,挂个我们自己的初始化函数。这个函数:

  1. 通过 ngx_http_conf_get_module_main_conf 获取 cmcf(core module conf)

  2. 然后通过 ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers) 将某个 phase 的 handlers 数组扩容,得到新容量位置的指针

  3. 然后将我们的 handler 放到指针指向的位置。

 1static ngx_int_t
 2ngx_http_hello_init(ngx_conf_t *cf)
 3{
 4        ngx_http_handler_pt        *h;
 5        ngx_http_core_main_conf_t  *cmcf;
 6
 7        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
 8
 9        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
10        if (h == NULL) {
11                return NGX_ERROR;
12        }
13
14        *h = ngx_http_hello_handler;
15
16        return NGX_OK;
17}

也有其它的方式。比如按需挂载、批量挂载等等。

运行

回顾

我们把一个 post conf 钩子的地址放到 ngx_http_module_t 中,再把这个 http_module 的地址放到 ngx_module_t 中。

然后把这个自定义的 module 的 config 文件写好,说明插件的名称、源码位置等等。这样可以把自定义模块编译通过 –add-module 到 nginx 中。

运行

当运行构建好的 NGX 时,首先在未正式启动前(例如 post config)时就会调用我们的钩子函数。在这些时候我们的 handler 就会被挂到一定的 PHASE。当 PHASE 触发(例如请求到来时),我们的 handler 就会被调用。