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 阶段,挂个我们自己的初始化函数。这个函数:
-
通过
ngx_http_conf_get_module_main_conf
获取 cmcf(core module conf) -
然后通过
ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers)
将某个 phase 的 handlers 数组扩容,得到新容量位置的指针 -
然后将我们的 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 就会被调用。