编写模块

想要学习如何开发一个 nginx 模块,最快速简单的方法莫过于写一个 Hello World 模块,没错,还真有这么一个 nginx-hello-world-module,而且 nginx.org 官网还介绍了这个模块。

首先,对于所有的 nginx 模块来说,都需要实现一个 ngx_module_t 结构体,如下所示,需要特别注意的是,如果去看 module 结构体的定义,它与下面的代码并不是一一对应的,这是因为 NGX_MODULE_V1 宏把其他变量都赋值了,帮我们屏蔽了一些细节。 总而言之,开发一个 nginx 模块,我们跟着这个套路走就行了。

ngx_module_t ngx_http_hello_world_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_world_module_ctx, /* module context */
    ngx_http_hello_world_commands, /* 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
};

其中第一个变量和最后一个变量都是固定的,我们不需要关心。如果开发的是 HTTP 模块,那么 module type 那儿写上 HTTP 的宏就行了,都是固定死的操作。

剩下来最重要的是设置 ctx 和 command (directives) 成员,command 成员,顾名思义,就是定义这个模块新增哪些 nginx.config 文件中的指令,比如 listen,include 等。

在我们这个 hello world 模块中,新增了一个 hello_world 指令,该指令不接受任何参数(NGX_CONF_NOARGS)。

static ngx_command_t ngx_http_hello_world_commands[] = {

    { ngx_string("hello_world"), /* directive */
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, /* location context and takes
                                            no arguments*/
      ngx_http_hello_world, /* configuration setup function */
      0, /* No offset. Only one context is supported. */
      0, /* No offset when storing the module configuration on struct. */
      NULL},

    ngx_null_command /* command termination */
};

接下来我们看到一个 configuration setup function, ngx_http_hello_world,这个函数意味着,当在 nginx.config 文件中,某个配置块中出现了 hello_world 指令时,nginx 将会调用 ngx_http_hello_world() 函数。

因为这个模块非常简单,我们就顺便看一眼这个函数。

static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf; /* pointer to core location configuration */

    /* Install the hello world handler. */
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_world_handler;

    return NGX_CONF_OK;
} 

说完了 command 成员,现在来说说 ctx 成员,这两个成员是一个 nginx 模块必须定义的。我们先看一下 hello world 模块是怎么定义 ctx 的。

static ngx_http_module_t ngx_http_hello_world_module_ctx = {
    NULL, /* preconfiguration */
    NULL, /* 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 */
};

ctx 的作用是当 nginx 启动,http 框架开始初始化时,把本模块的配置和 http{} server{} location{} 的配置整合使用的。hello world 的指令作为非常简单,仅仅只是返回一个字符串,不需要复杂的操作,因此全部为 NULL。 稍后我们会看一个复杂一点的例子。

最后就剩下 ngx_http_hello_world() 中的 ngx_http_hello_world_handler()函数了。它是告诉 nginx,在 HTTP 处理的 11 个阶段中,如果碰到了 hello_world 指令该如何处理,也就是我们这个模块真正做事情的部分。具体的实现大家可以自己下载源码看看,非常简单就不细说了。

编译成 nginx 动态模块

从 1.9 版本开始,nginx 支持了动态模块。在这之前如果我们要加载一个模块,是需要重新编译 nginx 的,而有了动态模块以后,我们只要把模块编译成一个 so 文件,然后在 nginx.config 里面用 load_module 加载就可以了。

nginx-hello-world-module 本身还没支持编译成动态模块,我们需要小小的修改一下。好在不需要修改源码,只要修改 config 文件就可以了。 以下是修改后的 config 文件,添加了 ngx_module_link 部分。

ngx_addon_name=ngx_http_hello_world_module

if test -n "$ngx_module_link"; then
    ngx_module_type=HTTP
    ngx_module_name=ngx_http_hello_world_module
    ngx_module_srcs="$ngx_addon_dir/ngx_http_hello_world_module.c"

    . auto/module
else
    HTTP_MODULES="$HTTP_MODULES ngx_http_hello_world_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_world_module.c"
fi

然后利用 nginx 源码把模块编译成 so 文件。

# ./configure --add-dynamic-module=/path-to/ngx_http_hello_world_module
# make 
# cd objs/
# ls 
ngx_http_hello_world_module.so

加载模块

最后修改 nginx 的配置文件

user www-data;
worker_processes 1;

load_module   "/path-to/ngx_http_hello_world_module.so";

events {
        worker_connections 65535;
}

http{
        location /test {
                hello_world;
        }
}

最后用 curl 访问 http://ip-address/test,会得到 “hello world”。

参考资料