ngx 配置解析

一 ngx配置解析框架

1.1 配置解析流程

配置解析源码在 ngx_conf_file.c文件中实现, 函数 ngx_conf_parse.

image.png

如上图所示, 配置解析采用递归的方式进行解析配置. 每次配置文件读取会读取一组数据, 其中第一个为配置项名称, 后面根据配置类型有不同数量的值. 示例 error_log /var/log/nginx/error.log; 其中键为 error_log 值为 /var/log/nginx/error.log. 除了普通的键值对类型, 还有递归类型 如:

image.png

当遇到递归类型配置时, 处理函数将会1.先保存配置解析的上下文. 2.递归调用配置解析函数. 3.恢复上下文.

1.2 配置文件读取

源码在 ngx_conf_file.c 文件的 ngx_conf_read_token 函数. 该函数主要实现 : a. 逐字符读取. b. 状态机处理字符. c. 有四种返回值, 错误 模块开始 模块结束 文件结束. d. 读取的键值对存放在 cf->args 数组中.

1.3 寻找配置处理函数

ngx_conf_t *cf; 数据结构理解 :

struct ngx_conf_s {
    char                 *name;  //配置文件名
    ngx_array_t          *args; //存储ngx_conf_read_token中读取的键值对

    ngx_cycle_t          *cycle; //全局的数据结构体
    ngx_pool_t           *pool; //全局内存池
    ngx_pool_t           *temp_pool; //临时内存池
    ngx_conf_file_t      *conf_file; //对应的配置文件结构体
    ngx_log_t            *log; //日志

    void                 *ctx; //描述当前配置解析的上下文(event模块用于指向对应的配置项)
    ngx_uint_t            module_type; //当前的模块类型 core event http mail
    ngx_uint_t            cmd_type; //当前的指令类型 

    ngx_conf_handler_pt   handler; //自定义的处理函数
    void                 *handler_conf; //自定义处理函数的数据
};

源码在ngx_conf_file.c文件的ngx_conf_handler函数中. 处理顺序 : 遍历所有模块的所有commands中依次1.匹配配置项名称. 2.匹配模块类型. 3.匹配指令类型. 4. 调用对应的处理函数.
在解析配置文件的过程中, 使用的始终是同一个 ngx_conf_t.

1.4 普通配置项的处理

通常是给指定配置项赋值, ngx提供了多种类型的赋值函数. 下面以 ngx_conf_set_num_slot为例.

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); // 将配置转成对应的类型 并赋值到对应的配置项
    if (*np == NGX_ERROR) {
        return "invalid number";
    }

    if (cmd->post) {
        post = cmd->post;
        return post->post_handler(cf, post, np); // 调用对应的处理函数
    }

    return NGX_CONF_OK;
}

其中cmf->offset通过 offsetof 宏获取字段在结构体中的偏移量.
其余的处理函数和上面的区别主要在于类型, 上面是针对 ngx_int_t类型, 还有 size_t, off_t, ngx_msec_t 等类型.

1.5 递归配置项的处理

递归配置项的处理流程: 1.保存当前上下文 2.设置上下文 3.调用配置解析函数. 4.恢复上下文. 其中上下文主要是 ngx_conf_t 中的 ctx, module_type, cmd_type. 参考 events 配置项的解析 :

ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                 *rv;
    void               ***ctx;
    ngx_uint_t            i;
    ngx_conf_t            pcf;
    ngx_event_module_t   *m;

    if (*(void **) conf) {
        return "is duplicate";
    }

    /* count the number of the event modules and set up their indices */
    //生成 event 配置项指针
    ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);

    ctx = ngx_pcalloc(cf->pool, sizeof(void *));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
    if (*ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *(void **) conf = ctx;

    // 依次调用 event 模块的配置生成函数
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->create_conf) {
            (*ctx)[cf->cycle->modules[i]->ctx_index] =
                                                     m->create_conf(cf->cycle);
            if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

    // 保存上下文
    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;

    // 递归调用配置解析
    rv = ngx_conf_parse(cf, NULL);

    // 恢复配置解析的上下文
    *cf = pcf;

    if (rv != NGX_CONF_OK) {
        return rv;
    }

    // 依次调用 event 模块的配置初始化函数
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->init_conf) {
            rv = m->init_conf(cf->cycle,
                              (*ctx)[cf->cycle->modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK) {
                return rv;
            }
        }
    }

    return NGX_CONF_OK;
}

二 配置存储

图片来源https://ialloc.org/blog/ngx-notes-conf-parsing/

image.png

在 ngx_cycle.c文件 ngx_init_cycle() 函数中, 创建配置存储数组.

    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
    if (cycle->conf_ctx == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }

配置获取, 以event 模块为例.

#define ngx_event_get_conf(conf_ctx, module)                                  \
             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index]

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]

四大模块 Core Event Http Mail. cyclt->conf_ctx数组中, event的数据实际存储在 ngx_events_module 对应下标的指针指向的数组中, 该指针数组的大小为Event模块的数量.

三 吐槽

ngx源码通过 void * 和函数指针把C语言的多态发挥的淋漓尽致. 同时, nginx 把业务抽象化, 模块化做的令人叹为观止. 但是ngx也有让我异常想吐槽的. 举例

1.枚举类型的边界. ngx_conf_t 结构体中有 module_type 和 cmd_type 两个字段. 其值本应是枚举类型, 通过位运算判断属于哪些类型. 但采用的是#define的方式, 导致很难确定具体有哪些 module_type 和 cmd_type. 类型的边界很难确定.

2.数据的边界. 以ngx非常重要的一个数据 ngx_cycle_s为例. ngx_cycle_s存储了一个进程所有的相关数据, 并且独一份. 这个结构体在各个业务模块中被使用, 导致你很难确定一个变量在哪些地方被使用. 对比 golang的做法, 以日志模块为例. 在 ngx 中 ngx_log_t 是全局独一份的日志数据, 其值被各个模块引用. 如果是 golang, 它的做法是把日志相关的数据封装在这个模块内, 提供日志记录接口. 如果实在有必要获取日志结构体, 也可以提供接口获取日志结构体.

吐槽到此为止, ngx确实有太多可以学习的地方, 但是依旧要以批判的眼光看待, 取其精华, 弃其糟粕.
当然 或许这就是大神和凡人的区别 但是自己作为一个凡人 一定不要没有大神的能力就玩些骚的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342