7 自定义Nginx模块

7.1 ngx_command_t 数组

commands 数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾是用ngx_numm_command表示。Nginx在解析配置文件中的一个配置项时首先会遍历所有的模块,对于每一个模块而言,即通过遍历commands数组进行,另外,在数组中检查到ngx_numm_command时,会停止使用当前模块解析该配置项。每一个ngx_command_t结构体定义个如下配置:

typedef struct ngx_command_s ngx_command_t;

struct ngx_command_s {
    //配置项名称,如"gzip"
    ngx_str_t       name;
    //配置项类型,type将制定配置项可以出现的位置,
    //例如:出现在server{}活location{}中,等等
    ngx_uint_t      type;
    //出现了name中指定的配置项后,将会调用set方法处理配置项的参数
    char            *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    //在配置文件中的偏移量
    ngx_uint_t      conf;
    //通常用于使用预设的解析方法解析配置项,这是配置模块的一个优秀的设计,
    //需要与conf配合使用
    ngx_uint_t      offset;
    //配置项读取后的处理方法,必须是ngx_conf_post_t结构的指针
    void            *post;
};

ngx_null_command只是一个空的ngx_command_s:

#define ngx_null_command    {ngx_null_string, 0, NULL, 0, 0, NULL}

也就是说,对于我们在nginx.cong 中编写的配置项mytest来说,nginx首先会遍历所有的模块(modules),而对于每个模块,会遍历他所对应的ngx_command_t数组,试图找到关于我们配置项目mytest的解析方式。

static ngx_command_t  ngx_http_mytest_commands[] =
{

    {
        ngx_string("mytest"),
        NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    ngx_null_command
};

7.2 command中用于处理配置项参数的set方法

static char *
ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    //首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
//结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
//http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
//请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
//实现的ngx_http_mytest_handler方法处理这个请求
    clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}

关于ngx_http_conf_get_module_loc_conf 的定义可以参考:

http://lxr.nginx.org/source/src/http/ngx_http_config.h#0065

本质: 就是设置ngx_http_mytest_handler, 匹配项被选中的时候, 应该如何解析。

7.3 定义ngx_http_module_t接口

这部分的代码, 是用于http框架的, 相当于http框架的回掉函数, 由于这里并不需要框架做任何操作。

static ngx_http_module_t  ngx_http_mytest_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 */
};

7.4 定义处理“mytest”command的handler

我们这里需要定义配置项被匹配之后的处理方法.

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    //必须是GET或者HEAD方法,否则返回405 Not Allowed
    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }

    //丢弃请求中的包体
    ngx_int_t rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK)
    {
        return rc;
    }

    //设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏
//ngx_string,它可以把ngx_str_t的data和len成员都设置好
    ngx_str_t type = ngx_string("text/plain");
    //返回的包体内容
    ngx_str_t response = ngx_string("Hello World!");
    //设置返回状态码
    r->headers_out.status = NGX_HTTP_OK;
    //响应包是有包体内容的,所以需要设置Content-Length长度
    r->headers_out.content_length_n = response.len;
    //设置Content-Type
    r->headers_out.content_type = type;

    //发送http头部
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
    {
        return rc;
    }

    //构造ngx_buf_t结构准备发送包体
    ngx_buf_t                 *b;
    b = ngx_create_temp_buf(r->pool, response.len);
    if (b == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    //将Hello World拷贝到ngx_buf_t指向的内存中
    ngx_memcpy(b->pos, response.data, response.len);
    //注意,一定要设置好last指针
    b->last = b->pos + response.len;
    //声明这是最后一块缓冲区
    b->last_buf = 1;

    //构造发送时的ngx_chain_t结构体
    ngx_chain_t     out;
    //赋值ngx_buf_t
    out.buf = b;
    //设置next为NULL
    out.next = NULL;

    //最后一步发送包体,http框架会调ngx_http_finalize_request方法
    //结束请求
    return ngx_http_output_filter(r, &out);
}

7.5 定义ngx_module_t中的mytest模块

定义HTTP模块方式很简单,例如:

ngx_module_t ngx_http_mytest_module;

其中ngx_module_t是一个Nginx模块的数据结构,下面来分析一下Nginx模块中的所有成员:

typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
  /*
  下面的ctx_index、index、spare0,spare1,
  spare2,spare3,version变量不需要再定义时候赋值,
  可以用Nginx准备好的宏NGX_MODULE_V1来定义,
  已经定义好了这7个值

    #define NGX_MODULE_V1  0,0,0,0,0,0,1

    对于一类模块(由下面的type成员决定类别)而言,
    ctx_index表示当前模块在这类模块中的序号。
    这个成员常常是由管理这类模块的一个Nginx核心模块设置的,
    对于所有的HTTP模块而言,ctx_index是由
    核心模块ngx_http_module设置的。
    ctx_index非常重要,Nginx的模块化设计非常依赖于各个模块的顺序,
    他们既用于表达优先级,也用于表明每个模块的位置,
    借以帮助Nginx框架快速获得某个模块的数据.
  */
  ngx_uint_t           ctx_index;


  /*
  index表示当前模块在ngx_module数组中的序号。
  注意,ctx_index表示的是当前模块在一类模块中的序号,
  而index表示当前模块在所有模块中的序号,同样很关键.
  Nginx启动时会根据ngx_modules数组设置各模块的index值,
  例如:
    ngx_max_module = 0;
    for(i = 0; ngx_module[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }
  */
  ngx_uint_t           index;


  //spare系列的保留变量,暂未使用
  ngx_uint_t           spare0;
  ngx_uint_t           spare1;
  ngx_uint_t           spare2;
  ngx_uint_t           spare3;

  //模块的版本,便于将来的拓展,目前只有一种,默认为1
  ngx_uint_t           version;


  /*
  ctx用于指向一类模块的上下文结构体,
  为什么需要ctx呢?因为前面说过,
  Nginx模块有许多种类,不同类模块之间的功能差别很大。
  例如,
  事件类型的模块主要处理I/O事件的相关功能,
  HTTP类型的模块主要处理HTTP应用层的功能。
  这样每个模块都有了自己的特性,
  而ctx会指向特定类型模块的公共接口。
  例如,
  在HTTP模块中,ctx需要指向ngx_http_module_t结构体
  */
  void                 *ctx;

  /*
  commands处理nginx.conf中的配置项
  */
  ngx_command_t        *commands;

  /*
  type表示该模块的类型,它与ctx指针是紧密相关的。
  在官方Nginx中,它的取值范围有以下5种:
  NGX_HTTP_MODULE
  NGX_CORE_MODULE
  NGX_CONF_MODULE
  NGX_EVENT_MODULE
  NGX_MAIL_MODULE
  */
  ngx_uint_t            type;

  /*
  在Nginx的启动、停止过程中,以下7个函数指针表示7个
  执行点分别用调用这7种方法。对于任意一个方法而言,
  如果不需要Nginx再某个时刻执行它,那么简单地把它设为NULL
  空指针即可。
  */

  /*
  虽然从字面上理解应当在master进程启动的时,回调
  init_master,但到目前为止,框架代码从来不会调动它,
  所以设置为NULL
  */
  ngx_int_t   (*init_master)(ngx_log_t *log);

  /*
  init_module回调方法在初始化所有模块时候被调用。
  在master/worker模式下,这个阶段将在启动worker子进程前完成。
  */
  ngx_int_t   (*init_module)(ngx_cycle_t *cycle);

  /*
  init_process 回调方法在正常服务前被调用。
  在master/worker模式下,多个worker子进程已经产生。
  在每个worker进程的初始化过程会调用所有模块的init_process函数
  */
  ngx_int_t   (*init_process)(ngx_cycle_t *cycle);

  /*
  由于Nginx暂时不支持多线程模式,所以init_thread在框架中
  没有被调用过,设置为NULL
  */
  ngx_int_t   (*init_thread)(ngx_cycle_t *cycle);

  /*
  同上、exit_thread也不支持,设为NULL
  */
  void        (*exit_thread)(ngx_cycle_t *cycle);

  /*
  exit_process回调方法在服务停止前被调用。
  在/master/worker模式下,worker进程会在退出前调用
  */
  void        (*exit_process)(ngx_cycle_t *cycle);


  /*
  exit_master回调方法将在master进程退出前被调用
  */
  void        (*exit_master)(ngx_cycle_t *cycle);


  /*
  一下8个spare_hook变量也是保留字段,目前没有使用,
  但可用Nginx提供的NGX_MODULE_V1_PADDING宏来填充
  #define NGX_MODULE_V1_PADDING 0,0,0,0,0,0,0,0
  */
  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;
};

所以在定义一个HTTP模块的时候,务必把type字段设置为NGX_HTTP_MODULE.

对于下列回调方法:init_module、init_process、exit_process、exit_master 调用他们的是Nginx框架代码。换句话说,这4个回调方法与HTTP框架无关。 即使nginx.conf中没有设置http{...}这种开启HTTP功能的配置项,这些 回调方法仍然会被调用。因此,通常开发HTTP模块时候都把他们设置为NULL。 这样Nginx不作为web服务器使用时,不会执行HTTP模块的任何代码。

定义HTTP时候,最重要的是要设置ctx和commands这两个成员。 对于HTTP类型模块来说,ngxmodule中的ctx指针必须指向ngx_http_module_t接口。

所以最终我们定义个ngx_module_t 模块如下:

ngx_module_t  ngx_http_mytest_module =
{
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,           /* module context */
    ngx_http_mytest_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
};

这样, mytest 模块在编译的时候, 就可以被加入到ngx_modules的全局数组中了。

7.6 完整代码 ngx_http_mytest_module.c

所以全部的编码工作完毕,最终的代码应该如下:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <sys/types.h>
#include <unistd.h>


//定义处理用户请求hello world handler
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    //必须是GET或者HEAD方法,否则返回405 Not Allowed
    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }

    //丢弃请求中的包体
    ngx_int_t rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK)
    {
        return rc;
    }

    //设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏
    //ngx_string,它可以把ngx_str_t的data和len成员都设置好
    ngx_str_t type = ngx_string("text/plain");
    //返回的包体内容
    ngx_str_t response = ngx_string("Hello World!");
    //设置返回状态码
    r->headers_out.status = NGX_HTTP_OK;
    //响应包是有包体内容的,所以需要设置Content-Length长度
    r->headers_out.content_length_n = response.len;
    //设置Content-Type
    r->headers_out.content_type = type;

    //发送http头部
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
    {
        return rc;
    }

    //构造ngx_buf_t结构准备发送包体
    ngx_buf_t                 *b;
    b = ngx_create_temp_buf(r->pool, response.len);
    if (b == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    //将Hello World拷贝到ngx_buf_t指向的内存中
    ngx_memcpy(b->pos, response.data, response.len);
    //注意,一定要设置好last指针
    b->last = b->pos + response.len;
    //声明这是最后一块缓冲区
    b->last_buf = 1;

    //构造发送时的ngx_chain_t结构体
    ngx_chain_t     out;
    //赋值ngx_buf_t
    out.buf = b;
    //设置next为NULL
    out.next = NULL;

    //最后一步发送包体,http框架会调用ngx_http_finalize_request方法
    //结束请求
    return ngx_http_output_filter(r, &out);
}


//定义command用于处理配置项参数的set方法
    static char *
ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    //首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据
    //结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个
    //http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    //http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果
    //请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们
    //实现的ngx_http_mytest_handler方法处理这个请求
    clcf->handler = ngx_http_mytest_handler;

    return NGX_CONF_OK;
}

//定义ngx mytest 配置匹配的command
static ngx_command_t  ngx_http_mytest_commands[] =
{

    {
        ngx_string("mytest"),
        NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
        ngx_http_mytest,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    ngx_null_command
};


//定义ngx_http_module_t接口
static ngx_http_module_t  ngx_http_mytest_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 */
};



//定义mytest模块
ngx_module_t  ngx_http_mytest_module =
{
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,           /* module context */
    ngx_http_mytest_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
};

7.7 配置文件 config

但是之后我们还需要给该模块相同目录下提供一个配置文件"config"表示在nginx编译的时候的一些编译选项。

ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

所以我们的模块编写完毕了,当前目录应该有两个文件

ls

config  ngx_http_mytest_module.c

7.8 重新编译Nginx 并添加自定义模块

进入Nginx源码目录 执行

./configure --prefix=/usr/local/nginx --add-module=/home/ace/openSource_test/nginx_module_http_test

--add-module为刚才自定义模块源码的目录

make 
sudo make install

测试自定义模块

修改nginx.conf文件

   server {
        listen 8777;

        server_name localhost;

        location / {
            mytest;#我们自定义模块的名称
        }
    }

重新启动nginx

打开浏览器输入地址和端口

results matching ""

    No results matching ""