nginx版本

nginx-1.22

针对问题

编写nginx模块的时候,有个数据结构,就是ngx_module_t数据结构,这个数据结构是整个模块的核心,本篇文章主要针对一般性的配置(http块配置)的整个加载过程,以及在整个加载配置过程中ngx_module_t中各个回调函数的意义。

nginx http块配置加载逻辑

首先说一下总体的加载过程模型,就是从配置文件中逐个字符进行扫描,然后扫描到一组配置或者一个配置块就进行配置解析,再次过程中进入不同配置块,会切换解析状态,如一开始最外层的解析NGX_CORE_MODULE模块配置,然后遇到http块进入http块配置就设置为解析NGX_HTTP_MODULE模块的配置。
也就是说整体上是个状态机类似的模型。

nginx NGX_CORE_MODULE配置解析

首先进入nginx的进程入口main函数,然后执行ngx_init_cycle()函数,该函数中一开始先解析NGX_CORE_MODULE类型的参数:

// 遍历所有 NGX_CORE_MODULE 类型模块,执行对应的create_conf结构并设置到cycle->conf_ctx中
    for (i = 0; cycle->modules[i]; i++) 
    {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) 
        {
            continue;
        }

        module = cycle->modules[i]->ctx;

        // 如果存在就执行
        if (module->create_conf)                
        {
            rv = module->create_conf(cycle);
            if (rv == NULL) 
            {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[cycle->modules[i]->index] = rv;
        }
    }

    // 进程环境变量
    senv = environ;

    // 初始化了一个core类型的配置数据结构
    ngx_memzero(&conf, sizeof(ngx_conf_t));
    /* STUB: init array ? */
    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
    if (conf.args == NULL) 
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    // 用于解析配置文件的临时内存池,解析完成后释放
    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (conf.temp_pool == NULL) 
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    conf.ctx = cycle->conf_ctx;
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;

#if 0
    log->log_level = NGX_LOG_DEBUG_ALL;
#endif

    // 复杂解析命令行参数 '-g' 加入的配置,这个接口是基于ngx_conf_parse进行实现的,没有'-g'参数直接跳过
    if (ngx_conf_param(&conf) != NGX_CONF_OK) 
    {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
    // 负责nginx配置文件的解析
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) 
    {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }

    if (ngx_test_config && !ngx_quiet_mode) 
    {
        ngx_log_stderr(0, "the configuration file %s syntax is ok",
                       cycle->conf_file.data);
    }

    for (i = 0; cycle->modules[i]; i++) 
    {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }

        module = cycle->modules[i]->ctx;

        if (module->init_conf) 
        {
            if (module->init_conf(cycle, cycle->conf_ctx[cycle->modules[i]->index]) == NGX_CONF_ERROR)
            {
                environ = senv;
                ngx_destroy_cycle_pools(&conf);
                return NULL;
            }
        }
    }

对于NGX_CORE_MODULE类型的模块:

typedef struct 
{
    ngx_str_t           name;
    /**
     * @brief 加载模块的时候执行,为NULL时就跳过,返回该模块的配置文件
     */
    void               *(*create_conf)(ngx_cycle_t *cycle);
    /**
     * @brief 
     * cycle -- ngx_cycle_t
     * conf -- create_conf返回的内存空间
     */
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

整个过程对于一个模块简单来说就是:

ngx_core_module_t->create_conf();
ngx_command_t->set();
ngx_core_module_t->init_conf();

NGX_HTTP_MODULE

ngx_http_module,整个模块是个NGX_CORE_MODULE类型的模块,在ngx_command_t->set();过程中会调用函数ngx_http_block(),这个函数中会针对http模块的配置进行空间初始化。所有http模块的配置最终会以这个数据结构为根将所有的配置都设置上去,对应的数据结构就是:

/**
 * @brief 这个数据结构的是在ngx_command_t->set回调函数中创建数据结构并初始化的,NGX_CORE_MODULE机制的create接口直接指定了NULL
 */
typedef struct {
    // 指向指针数组,为NGX_HTTP_MODULE模块上下文数据结构ngx_http_module_t调用create_main_conf回调函数创建的配置内存空间,与NGX_HTTP_MODULE类型模块配对是基于模块编号(ngx_module_t->ctx_index)
    void        **main_conf;  

    // 指向指针数组,ngx_http_module_t->create_srv_conf
    void        **srv_conf;                 
    
    // 指向指针数组,ngx_http_module_t->create_loc_conf
    void        **loc_conf;
} ngx_http_conf_ctx_t;

然后基于这个内存配置数据结构进行下面的配置:

/*
     * create the main_conf's, the null srv_conf's, and the null loc_conf's of the all http modules
     */
    // 遍历所有的NGX_HTTP_MODULE
    for (m = 0; cf->cycle->modules[m]; m++) 
    {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE)
        {
            continue;
        }

        // 获取NGX_HTTP_MODULE类型的模块,NGX_HTTP_MODULE类型的模块的上下文数据结构就是ngx_http_module_t类型
        module = cf->cycle->modules[m]->ctx;
        // 获取模块对应的http配置index
        mi = cf->cycle->modules[m]->ctx_index;

        if (module->create_main_conf) 
        {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) 
            {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf) 
        {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) 
            {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_loc_conf) 
        {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) 
            {
                return NGX_CONF_ERROR;
            }
        }
    }

    pcf = *cf;

    // 设置配置内存地址,原本的cf->ctx指向的是ngx_cycle_t->conf_ctx,进行http配置的时候就就将配置地址设置为http的
    cf->ctx = ctx;

    // 遍历所有NGX_HTTP_MODULE模块
    for (m = 0; cf->cycle->modules[m]; m++) 
    {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) 
        {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;            // 获取上下文
        if (module->preconfiguration) 
        {
            if (module->preconfiguration(cf) != NGX_OK) 
            {
                return NGX_CONF_ERROR;
            }
        }
    }

    /**
     * @brief parse inside the http{} block
     * 只对HTTP模块的HTTP_MAIN_CONF进行配置解析,如果不是目标配置就不进行指令set的回调函数调用。完成过滤。
     */
    cf->module_type = NGX_HTTP_MODULE;          // 解析http模块
    cf->cmd_type = NGX_HTTP_MAIN_CONF;          // 解析NGX_HTTP_MAIN_CONF位置配置

    // 对NGX_HTTP_MODULE、NGX_HTTP_MAIN_CONF进行配置,注意这里有个点,就是ngx_conf_parse配置是在加载完过NGX_CORE_MODULE模块之后再进入该函数加载http的模块参数,此时的cf->conf_file->file.fd已经在加载NGX_CORE_MODULE配置时进行了相应设置,也就是说进入该函数后type = parse_block;即解析配置块。
    // 解析块配置和解析NGX_CORE_MODULE模块配置差不多,最终调用的就是模块指定的set回调函数。
    rv = ngx_conf_parse(cf, NULL);

    if (rv != NGX_CONF_OK) 
    {
        goto failed;
    }

    /**
     * init http{} main_conf's, merge the server{}s' srv_conf's and its location{}s' loc_conf's
     * 上面完成了NGX_HTTP_MODULE的加载
     */
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    // 遍历所有的NGX_HTTP_MODULE模块
    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;

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

        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) 
        {
            goto failed;
        }
    }

整体概括起来和NGX_CORE_MODULE比较相似,但是比NGX_CORE_MODULE的更为复杂,NGX_HTTP_MODULE的配置分为了三层:NGX_HTTP_MAIN_CONF,NGX_HTTP_SER_CONF和NGX_HTTP_LOC_CONF。就是说通过设置这几个参数可以指定配置文件中对应指令出现的位置。这三层并不是指的ngx_http_conf_ctx_t中3个指针数组,要理解这三层,首先要理解一下http、server和loc之间的关系:
http的数据结构为:

typedef struct {
    ngx_array_t                servers;         /* ./struct ngx_http_core_srv_conf_t,描述一个server配置 */

    ngx_http_phase_engine_t    phase_engine;

    ngx_hash_t                 headers_in_hash;

    ngx_hash_t                 variables_hash;

    ngx_array_t                variables;         /* ngx_http_variable_t */
    ngx_array_t                prefix_variables;  /* ngx_http_variable_t */
    ngx_uint_t                 ncaptures;

    ngx_uint_t                 server_names_hash_max_size;
    ngx_uint_t                 server_names_hash_bucket_size;

    ngx_uint_t                 variables_hash_max_size;
    ngx_uint_t                 variables_hash_bucket_size;

    ngx_hash_keys_arrays_t    *variables_keys;

    ngx_array_t               *ports;

    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

server的数据结构为:

typedef struct 
{
    /* array of the ngx_http_server_name_t, "server_name" directive */
    ngx_array_t                 server_names;

    /** server ctx
     * 每个server配置块都会再创建一个ngx_http_conf_ctx_t类型,其中ngx_http_conf_ctx_t->main_conf指向http模块的main_conf地址 */
    ngx_http_conf_ctx_t        *ctx;

    u_char                     *file_name;
    ngx_uint_t                  line;

    ngx_str_t                   server_name;

    size_t                      connection_pool_size;
    size_t                      request_pool_size;
    size_t                      client_header_buffer_size;

    ngx_bufs_t                  large_client_header_buffers;

    ngx_msec_t                  client_header_timeout;

    ngx_flag_t                  ignore_invalid_headers;
    ngx_flag_t                  merge_slashes;
    ngx_flag_t                  underscores_in_headers;

    unsigned                    listen:1;
#if (NGX_PCRE)
    unsigned                    captures:1;
#endif

    ngx_http_core_loc_conf_t  **named_locations;
} ngx_http_core_srv_conf_t;

server的配置信息是存储在ngx_http_core_main_conf_t->servers数组中的,locations是存储在ngx_http_core_srv_conf_t->named_locations中的。在数据结构层次上分为三层,而上述的三层指的是这里的三层,一个指令可以通过NGX_HTTP_MAIN_CONF,NGX_HTTP_SER_CONF和NGX_HTTP_LOC_CONF的指定出现在http配置块、server配置块或者location配置块中,然后加载到对应的数据结构中。
这样就有了个问题:如果一个指令同时出现在这三层,该使用哪个位置的参数。这个问题可以通过下面的接口解决:

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);                // 加载conf之前
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);               // 加载conf之后

    void       *(*create_main_conf)(ngx_conf_t *cf);                        // http模块在执行set回调函数的时候进行调用,将创建好的
    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);                         // 为特定location配置来分配内存
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);  // 设置默认值以及合并继承过来的配置值
} ngx_http_module_t;

这个merge的概念下面单独列出。
merge_srv_conf这个接口用来处理http配置块中的参数与server配置块中参数的merge。
merge_loc_conf这个接口用来处理http配置块中的参数与location配置块中参数的merge、以及server配置块中的参数与location配置块中参数的merge。

关于merge

以NGX_CORE_MODULE参数加载过程为例,整个过程就是:

ngx_core_module_t->create_conf();
ngx_command_t->set();
ngx_core_module_t->init_conf();

其中create_conf()负责创建内存配置空间,set()回调负责解析配置文件将配置文件中的参数设置到内存配置中;最后面的init_conf可以理解为merge,因为其内部处理一般是针对set中未设置的数据进行默认初始化 – 即merge配置文件参数和默认初始化参数。