Nginx源码分析模块化

断断续续读了近半个多月Nginx的源码,感觉还是比较吃力,因为Nginx模块化的设计带来的是复杂的结构体以及回调函数,逻辑比较复杂。到现在也不知道改怎么写好Nginx源码分析这类博客,但是只能硬着头皮去写,希望写的过程中能出现各种疑问从而解决。目前还没完整地读完源码,按照Nginx执行顺序写还力不从心,所以先从如何编写三方模块开始。

在之前一篇文章写了一个Nginx删除域名Cache的三方模块 http://www.firefoxbug.com/?p=1985,Nginx源码分析就从这个模块进行分析。至于什么是模块?我们从Nginx的配置文件出发来解释。

event {
      use epoll;
}
http {
    server {
        server_name www.firefoxbug.net;
        access_log /home/test.log;
    }
}

看上面的配置文件,event http server server_name都可以叫做一个模块。server_name access_log也叫做指令,后面跟着参数。暂时可以理解为函数,server_name就是一个函数,后面跟着的就是参数。因此要写一个http的三方模块,首先呢要定义一个模块,下面都以前文的move_domain为例子做解析。

定义模块


ngx_module_t来定义一个新加入的模块,命名规范是ngx_http__module.
ngx_module_t  ngx_http_move_domain_cache_module = { 
    NGX_MODULE_V1, 
    &ngx_http_move_domain_cache_module_ctx,              /* module context */ 
    ngx_http_move_domain_cache_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 
};

按这个接口定义好一个模块,接下来还要指定模块上下问(上面结构体的/* module context */)和模块包含的指令(/* module directives */ )

定义模块上下文


ngx_http_module_t定义模块的上下文,命名规范ngx_http__module_ctx.
typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);  
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf); 

    void       *(*create_main_conf)(ngx_conf_t *cf);  
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

ngx_http_module_t就是一个静态的变量,里面是一些列的回调函数,我们只需要在需要的阶段设置就行,不需要阶段设置为NULL。下面是各个回调函数的含义。
preconfiguration:在创建和读取模块配置信息之前调用
postconfiguration:在创建和读取模块配置信息之后调用
create_main_conf:创建http模块(http{...})的配置信息结构体
init_main_conf:初始化本模块位于http block的配置信息存储结构
create_srv_conf:创建本模块位于http server block的配置信息存储结构,每个server block会创建一个
merge_srv_conf:因为有些配置指令即可以出现在http block,也可以出现在http server block中。那么遇到这种情况,每个server都会有自己存储结构来存储该server的配置,但是在这种情况下当在http block中的配置与server block中的配置信息冲突的时候,就需要调用此函数进行合并,该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。
create_loc_conf:调用该函数创建本模块位于location block的配置信息存储结构。每个在配置中指明的location创建一个
merge_loc_conf:与merge_srv_conf类似,这个也是进行配置值合并的地方

下面看看前文ngx_http_move_domain_cache_module的模块上下文定义
static ngx_http_module_t  ngx_http_move_domain_cache_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 */ 
}; 

定义模块指令


ngx_command_t定义模块的上下文,命名规范ngx_http__module_ctx.
typedef struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
}ngx_command_t;

模块指令就是上面说的“函数”,server_name或者access,以及listen之类的都是指令,一个模块下都有自己的指令。下面是各个参数解释。
name:配置指令的名称。
type:该配置的类型,指令属性的集合,nginx提供了很多预定义的属性值,可以查找。
set:这是一个回调函数,读取配置文件得到的指令名字一旦和上面的name相等,就会调用set回调函数来将配置的参数写入模块自定义配置变量的结构体。还有就是可能把真正的指令功能处理函数注册到Nginx的模块里,一旦请求与此location相匹配就会调用处理函数。
conf:去内存里面找相应的模块,一般都是NGX_HTTP_MODULE
offset:该配置项的精确存放位置,一般都是偏移。用offsetof(A, b)宏来表示A结构体中的b偏移量
post:指针,可以灵活变化

下面看看前文ngx_http_move_domain_cache_module的模块指令定义
/* Commands */ 
static ngx_command_t  ngx_http_move_domain_cache_commands[] = { 
    { ngx_string("mv_cache"), 
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, 
      ngx_http_move_domain_cache, 
      0, 
      0, 
      NULL }, 
    ngx_null_command 
}; 

mv_cache是指令名字,NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS指定指令只能出现在location里面,并且无参数。Nginx初始化模块时,通过读取配置文件,然后遍历此模块的ngx_command_t的模块名,匹配后就调用回调函数ngx_http_move_domain_cache,下面说下这个回调函数 set 的参数。
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
cf: 该参数里面保存里读取到的配置信息的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个ngx_str_t类型的数组,每个数组元素。该数组的首个元素是这个配置指令本身的字符串,第二个元素是首个参数,第三个元素是第二个参数,依次类推。
cmd: 这个配置指令对应的ngx_command_t结构。传入这个参数的目的就是为了遍历模块名字。
conf: 就是定义的存储这个配置值的结构体,用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。

下面看一个Nginx内建的 set 函数:
    // 对worker的数量进行设置
char *
ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char  *p = conf;

    ngx_int_t        *np;
    ngx_str_t        *value;
    ngx_conf_post_t  *post;


    np = (ngx_int_t *) (p + cmd->offset);

    if (*np != NGX_CONF_UNSET) {
        return "is duplicate";
    }

    value = cf->args->elts;
    *np = ngx_atoi(value[1].data, value[1].len); //把value后面的buffer强制转为一个str
    if (*np == NGX_ERROR) {
        return "invalid number";
    }

    if (cmd->post) {
        post = cmd->post;
        return post->post_handler(cf, post, np);
    }

    return NGX_CONF_OK;
}

代码就是nginx.conf中“worker_processes 4;”中的worker_processes指令,再加载这个模块时会把字符串“4”变成数字4,然后保存在相应的模块配置里面。

标签:C/C++, Nginx

评论已关闭