nginx http模块开发(5) - 配置解析

2018-07-21 11:02:44

访问配置数据

从前几篇文章我们知道 nginx 使用多个 ngx_http_conf_ctx_t 结构表示不同的配置层次,每个结构代表一个块的配置,所有http模块的配置数据都存储在结构里的 main_conf / srv_conf / loc.conf数组里,并且每个数组都以直接或者间接的方式存储了模块相关配置信息,所以可以用 "ctx.xxx_conf[module.ctx_index]" 的形式访问任意http模块当前的正确配置数据,完全不需要关心当前所在的配置块位置。

为了简化操作 Nginx 定义了三个宏,用来在配置阶段直接获得http模块在当前ctx下的配置数据:

// 从配置的ctx数组里获取模块的配置,三个层次
#define ngx_http_conf_get_module_main_conf(cf, module)                \
    ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]

#define ngx_http_conf_get_module_srv_conf(cf, module)                 \
    ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]

#define ngx_http_conf_get_module_loc_conf(cf, module)                 \
    ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]

另外在处理 http 请求时,nginx 框架也会把配置数组放在 ngx_http_request_t 对象里

struct ngx_http_request_s {
    ...
    void    **main_conf;
    void    **srv_conf;
    void    **loc_conf;
    ...
}

可以用下列的宏获取

#define ngx_http_get_module_main_conf(r, module) \
    (r)->main_conf[module.ctx_index]

#define ngx_http_get_module_srv_conf(r, module)  \
    (r)->srv_conf[module.ctx_index]

#define ngx_http_get_module_loc_conf(r, module)  \
    (r)->loc_conf[module.ctx_index]


确定配置数据位置

nginx 在 ngx_http_block 里调用 create_xxx_conf 为所有的http模块创建好存放数据的结构并放在特定的位置。

下面是 ngx_http_block 函数的部分代码。

for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;

        // 创建每个模块的main_conf
        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        // 创建每个模块的srv_conf
        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        // 创建每个模块的loc_conf
        if (module->create_loc_conf) {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

我们以 ngx_http_log_module http模块为例,它在http模块里排第2,那么上面的 mi=1。

static ngx_http_module_t  ngx_http_log_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_log_init,                     /* postconfiguration */

    ngx_http_log_create_main_conf,         /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_log_create_loc_conf,          /* create location configuration */
    ngx_http_log_merge_loc_conf            /* merge location configuration */
};

从上面可以看出,该模块定义了 create_main_conf 和 create_srv_conf 两个函数,那么他们就会在 上面ngx_http_block 函数循环里分别被调用。

static void *
ngx_http_log_create_main_conf(ngx_conf_t *cf)
{
    ngx_http_log_main_conf_t  *conf;

    ...

    return conf;
}
static void *
ngx_http_log_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_log_loc_conf_t  *conf;

    ...

    return conf;
}

从函数定义可以看出,调用后分别返回 ngx_http_log_main_conf_tngx_http_log_loc_conf_t 结构体,然后存放在相应位置,大致如下图


存储位置宏

nginx 定义了3个宏,获取 ngx_http_conf_ctx_t 结构内部的偏移量。

#define NGX_HTTP_MAIN_CONF_OFFSET  offsetof(ngx_http_conf_ctx_t, main_conf)
#define NGX_HTTP_SRV_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, srv_conf)
#define NGX_HTTP_LOC_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, loc_conf)

我们还是以 ngx_http_log_module 为例,它的其中一个指令 access_log 配置如下。

static ngx_command_t  ngx_http_log_commands[] = {
    { ngx_string("access_log"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                        |NGX_HTTP_LMT_CONF|NGX_CONF_1MORE,
      ngx_http_log_set_log,  //set 方法
      NGX_HTTP_LOC_CONF_OFFSET,  //conf
      0,
      NULL },

    ...
};

当我们解析到该指令的时候,会调用 ngx_conf_handler 进行解析。

cmd  即为上面的 ngx_command_t。

cf->ctx  即指向上图的 main_conf。

    //即把指针移到了上图的 loc_conf。
    confp = *(void **) ((char *) cf->ctx + cmd->conf);
    if (confp) {
        conf = confp[cf->cycle->modules[i]->ctx_index];
    }

cf->cycle->modules[i]->ctx_index 即为该模块的序号,是第2个http模块,即为1。所以最终 conf 指向的是上图的最后一行方框1指向的  ngx_http_log_loc_conf_t 结构体

然后调用通过如下语句开始存储该指令。

rv = cmd->set(cf, cmd, conf);

即调用上面配置的 ngx_http_log_set_log 函数,由于conf已经指向 ngx_http_log_loc_conf_t,所以可以直接使用,而如果要使用到  ngx_http_log_main_conf_t 结构体,则要使用到上面介绍的宏来实现。

static char *
ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_log_loc_conf_t *llcf = conf;
    
    value = cf->args->elts; //指令的所有参数
    ...

    //通过宏来获取
    ngx_http_log_main_conf_t          *lmcf;
    lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
    
    ...
}


配置解析函数

在上面提到的 ngx_command_t 里配置解析函数 set() 的原型如下:

char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

它有3个参数:

cf  是当前指令的解析环境数据,args 成员存储了指令的所有参数。
cmd  是当前指令的描述信息,也就是 ngx_command_t 数组里的元素。
conf  是当前层次的 ngx_http_conf_ctx_t 数组里存放的配置数据结构。

通常使用 cf(提供配置信息) 和 conf(提供存储位置) 这两个参数,再加上一些字符串处理代码,就可以完成对一条指令的解析工作。

注意它的返回值是 char *,如果出错,直接返回错误信息的字符串,否则需要返回 NGX_CONF_OK,即 NULL。

nginx 预定义了 14 个配置解析函数,下面是几个常用的函数,这些函数的名称均是 "ngx_conf_xxx_slot" 的形式。

ngx_conf_set_flag_slot :处理值 on|off,转化为1|0;

ngx_conf_set_str_slot:处理一个参数,转化为 ngx_str_t;  

ngx_conf_set_str_array_slot:处理多个参数,转化为 ngx_str_t 数组;

ngx_conf_set_keyval_slot:处理多个参数,转化为 ngx_keyval_t 数组;

ngx_conf_set_num_slot:处理一个参数,转化为整数;

ngx_conf_set_size_slot:处理一个参数,转化为整数,可以使用单位 k/m;

ngx_conf_set_msec_slot:处理一个参数,转化为毫秒数,可以使用单位 m/h/d/w/M/y等。

ngx_conf_set_sec_slot:同上,转化为秒。

使用这些函数必须把值初始化为 UNSET 值:

#define NGX_CONF_UNSET       -1
#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR   (void *) -1
#define NGX_CONF_UNSET_SIZE  (size_t) -1
#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1

解析函数的实现

想要自己实现解析函数,最好看一下上面几个预定义的解析函数的源码,可以让自己更清晰。

下面我们拿最简单的 ngx_conf_set_flag_slot  为例讲解下,其他的读者自己去看源码,原理都差不多。

char *
ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char  *p = conf;

    ngx_str_t        *value;
    ngx_flag_t       *fp;
    ngx_conf_post_t  *post;

    fp = (ngx_flag_t *) (p + cmd->offset); //使用偏移量得到存储位置

    if (*fp != NGX_CONF_UNSET) { //必须初始化为 NGX_CONF_UNSET
        return "is duplicate";
    }

    value = cf->args->elts; //获取参数字符串数组

    //大小写无关的比较
    if (ngx_strcasecmp(value[1].data, (u_char *) "on") == 0) {
        *fp = 1;

    } else if (ngx_strcasecmp(value[1].data, (u_char *) "off") == 0) {
        *fp = 0;

    } else {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                     "invalid value \"%s\" in \"%s\" directive, "
                     "it must be \"on\" or \"off\"",
                     value[1].data, cmd->name.data);
        return NGX_CONF_ERROR;
    }

    //配置解析完成后的回调函数
    if (cmd->post) {
        post = cmd->post;
        return post->post_handler(cf, post, fp);
    }

    return NGX_CONF_OK;
}


配置合并

解析完所有配置后,nginx 的配置解析工作并没有结束,http/server/location 等多个层次里的配置数据还需要合并,这样高层的配置就可以当做默认值传递给低层。

合并操作是在 ngx_http_block 里解析完 server{} 块后开始调用。

    // 初始化每个http模块的main配置
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;

        // 初始化main配置
        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        // 合并srv配置
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }

在 ngx_http_module_t 定义了两个合并函数指针:

typedef struct {
    char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

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

} ngx_http_module_t;

第一个参数 cf 通常不会使用,prev表示上层的配置数据,conf表示当前层次的配置数据。

合并操作通常是逐个检查当前结构的值,如果是未设置(UNSET),那么就会使用上层的值。

合并操作必须由我们自己实现,因为nginx不知道我们的数据结构。


配置指令的类型

ngx_command_t 的成员 type 是一个标志量,确定了指令所能出现的作用域、参数的数量和类型,可以使用 | 来指定多个标志。

决定作用域的宏定义:

// http/ngx_http_config.h
#define NGX_HTTP_MAIN_CONF    0x02000000  //可以出现在 http块
#define NGX_HTTP_SRV_CONF     0x04000000  //可以出现在 server块
#define NGX_HTTP_LOC_CONF     0x08000000  //可以出现在 location块
#define NGX_HTTP_UPS_CONF     0x10000000  //可以出现在 upstream块
...

决定参数数量的宏定义:

// nginx指令可以接受的参数数量
#define NGX_CONF_NOARGS      0x00000001  //没有参数
#define NGX_CONF_TAKE1       0x00000002  //1个参数
#define NGX_CONF_TAKE2       0x00000004  //2个参数
#define NGX_CONF_TAKE3       0x00000008
#define NGX_CONF_TAKE4       0x00000010
#define NGX_CONF_TAKE5       0x00000020
#define NGX_CONF_TAKE6       0x00000040
#define NGX_CONF_TAKE7       0x00000080

// 最多只能接受8个参数
#define NGX_CONF_MAX_ARGS    8

// 1个或2个参数
#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2)

//1个或3个参数
#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3)

#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3)

#1个或2个或3个参数
#define NGX_CONF_TAKE123     (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3   \
                              |NGX_CONF_TAKE4)
// 特殊的指令属性
#define NGX_CONF_ARGS_NUMBER 0x000000ff  //只接受数字参数
#define NGX_CONF_BLOCK       0x00000100  //指令是配置块,即{...}
#define NGX_CONF_FLAG        0x00000200  //指令接受on/off,转化为ngx_flag_t
#define NGX_CONF_ANY         0x00000400  //不限制参数数量
#define NGX_CONF_1MORE       0x00000800  //参数数量必须超过1个
#define NGX_CONF_2MORE       0x00001000  //参数数量必须超过2个

 

备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/ce


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。