NGINX(四)配置解析

前言

nginx配置解析是在初始化ngx_cycle_t数据结构时,首先解析core模块,然后core模块依次解析自己的子模块。

配置解析过程

nginx调用ngx_conf_parse函数进行配置文件解析,下面是核心代码,函数首先打开配置文件,然后循环调用ngx_conf_read_token读取一行配置,解析出来保存在cf->args中,如果遇到";" 或者 "{", 在文件没有结束和错误情况下,则会回到ngx_conf_parse中,继续执行ngx_conf_handler函数。

char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename )
{
    char             *rv;
    ngx_fd_t          fd ;
    ngx_int_t         rc ;
    ngx_buf_t         buf ;
    ngx_conf_file_t  *prev, conf_file;
    enum {
        parse_file = 0 ,
        parse_block ,
        parse_param
    } type ;

    if (filename) {
        /* 打开配置文件 */
        fd = ngx_open_file(filename->data , NGX_FILE_RDONLY, NGX_FILE_OPEN, 0 );
        if ( fd == NGX_INVALID_FILE ) {
            ngx_conf_log_error (NGX_LOG_EMERG, cf, ngx_errno ,
                               ngx_open_file_n " "%s" failed",
                               filename ->data);
            return NGX_CONF_ERROR;
        }

        prev = cf-> conf_file;

        cf ->conf_file = &conf_file ;

        if ( ngx_fd_info(fd , & cf->conf_file->file .info) == NGX_FILE_ERROR) {
            ngx_log_error (NGX_LOG_EMERG, cf->log , ngx_errno,
                          ngx_fd_info_n " "%s" failed", filename->data);
        }

        cf ->conf_file->buffer = &buf;

        buf .start = ngx_alloc(NGX_CONF_BUFFER , cf-> log);
        if ( buf.start == NULL ) {
            goto failed;
        }

        buf .pos = buf.start ;
        buf .last = buf.start ;
        buf .end = buf.last + NGX_CONF_BUFFER;
        buf .temporary = 1;

        cf ->conf_file->file.fd = fd;
        cf ->conf_file->file.name .len = filename->len ;
        cf ->conf_file->file.name .data = filename->data ;
        cf ->conf_file->file.offset = 0 ;
        cf ->conf_file->file.log = cf-> log;
        cf ->conf_file->line = 1;

        type = parse_file;

    } else if (cf->conf_file ->file. fd != NGX_INVALID_FILE ) {

        type = parse_block;

    } else {
        type = parse_param;
    }


for ( ;; ) {
    rc = ngx_conf_read_token (cf);

    /*
     * ngx_conf_read_token() may return
     *
     *    NGX_ERROR             there is error
     *    NGX_OK                the token terminated by ";" was found
     *    NGX_CONF_BLOCK_START  the token terminated by "{" was found
     *    NGX_CONF_BLOCK_DONE   the "}" was found
     *    NGX_CONF_FILE_DONE    the configuration file is done
     */

    if (rc == NGX_ERROR) {
         goto done;
    }

    if (rc == NGX_CONF_BLOCK_DONE) {

         if ( type != parse_block ) {
            ngx_conf_log_error (NGX_LOG_EMERG, cf, 0, "unexpected "}"");
             goto failed;
         }

         goto done;
    }

    if (rc == NGX_CONF_FILE_DONE) {

         if ( type == parse_block ) {
            ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                               "unexpected end of file, expecting "}"");
             goto failed;
         }

         goto done;
    }

    if (rc == NGX_CONF_BLOCK_START) {

         if ( type == parse_param ) {
            ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                               "block directives are not supported "
                               "in -g option");
             goto failed;
         }
    }

    /* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */

    if (cf-> handler) {

         /*
         * the custom handler, i.e., that is used in the http's
         * "types { ... }" directive
         */

         if ( rc == NGX_CONF_BLOCK_START ) {
            ngx_conf_log_error (NGX_LOG_EMERG, cf, 0, "unexpected "{"");
             goto failed;
         }

        rv = (* cf->handler)(cf , NULL , cf-> handler_conf);
         if ( rv == NGX_CONF_OK ) {
             continue;
         }

         if ( rv == NGX_CONF_ERROR ) {
             goto failed;
         }

        ngx_conf_log_error (NGX_LOG_EMERG, cf, 0, rv );

         goto failed;
    }


    rc = ngx_conf_handler (cf, rc);

    if (rc == NGX_ERROR) {
         goto failed;
    }
}

failed:

    rc = NGX_ERROR ;

done:

    if (filename) {
        if ( cf->conf_file->buffer ->start) {
            ngx_free (cf-> conf_file->buffer ->start);
        }

        if ( ngx_close_file(fd ) == NGX_FILE_ERROR) {
            ngx_log_error (NGX_LOG_ALERT, cf->log , ngx_errno,
                          ngx_close_file_n " %s failed",
                          filename ->data);
            return NGX_CONF_ERROR;
        }

        cf ->conf_file = prev;
    }

    if (rc == NGX_ERROR) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK ;
}

ngx_conf_handler函数如下,函数会遍历所有模块,并匹配配置命令key,取出配置上下文指针,执行相应的set函数。

static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    char           *rv;
    void           *conf, **confp;
    ngx_uint_t      i , found;
    ngx_str_t      *name;
    ngx_command_t  *cmd;

    name = cf ->args-> elts;

    found = 0;

    /*遍历所有模块,并比较模块中所有配置的key值*/
    for (i = 0 ; ngx_modules[i]; i ++) {

        cmd = ngx_modules[i]->commands ;
        if ( cmd == NULL) {
            continue;
        }

        for ( /* void */ ; cmd->name .len; cmd++) {

            if ( name->len != cmd-> name.len ) {
                continue;
            }
             /*比较命令key值是否和配置的一致*/
            if ( ngx_strcmp(name ->data, cmd->name .data) != 0) {
                continue;
            }

            found = 1 ;
            
             /*过滤掉非指定模块,进入ngx_conf_parse前,会指定module_type值,解析指定模块*/
            if ( ngx_modules[i ]->type != NGX_CONF_MODULE
                && ngx_modules[i]->type != cf-> module_type)
            {
                continue;
            }

            /*判断解析的配置命令类型是否和指定的类型一致,cmd_type在进入ngx_conf_parse前指定*/

            if (!( cmd->type & cf-> cmd_type)) {
                continue;
            }

            if (!( cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
                ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                                  "directive "%s" is not terminated by ";"",
                                  name ->data);
                return NGX_ERROR;
            }

            if (( cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
                ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                                   "directive "%s" has no opening "{"",
                                   name ->data);
                return NGX_ERROR;
            }

            /*判断配置参数个数是否正确*/

            if (!( cmd->type & NGX_CONF_ANY)) {

                if ( cmd->type & NGX_CONF_FLAG) {

                    if ( cf->args->nelts != 2 ) {
                        goto invalid;
                    }

                } else if (cmd ->type & NGX_CONF_1MORE) {

                    if ( cf->args->nelts < 2 ) {
                        goto invalid;
                    }

                } else if (cmd ->type & NGX_CONF_2MORE) {

                    if ( cf->args->nelts < 3 ) {
                        goto invalid;
                    }

                } else if (cf ->args-> nelts > NGX_CONF_MAX_ARGS ) {

                    goto invalid;

                } else if (!(cmd ->type & argument_number[cf ->args-> nelts - 1 ]))
                {
                    goto invalid;
                }
            }

            /* 获取指定配置上下文的指针,*/

            conf = NULL ;

            if ( cmd->type & NGX_DIRECT_CONF) {
                 /*
                 *取ngx_core_module模块配置ngx_core_conf_t指针,由于该模块结构中直接配置了create_conf回调函数,已经分配过内存,直接使用
                 */
                conf = (( void **) cf->ctx )[ngx_modules[i]->index ];

            } else if (cmd ->type & NGX_MAIN_CONF) {
                 /*
                 *假设cmd->name值是http,即正在解析http{}块,(((void **) cf->ctx)[ngx_modules[i]->index]取出的是ngx_http_module模块配置ngx_http_conf_ctx_t的指针,
                 *由于配置还未进行内存分配,因此这里取了指针的地址,以备后面ngx_http_block对其进行内存分配
                 */
                conf = &((( void **) cf->ctx )[ngx_modules[i]->index ]);

            } else if (cf ->ctx) {
                 /*
                 *假设cmd->name值是location,并出现在server{}第一层中,即解析server { location{} }块,首先confp取出的是ngx_http_conf_ctx_t中srv_conf指针值,
                 *conf则取出当前模块对应的http配置指针
                 */
                confp = *( void **) ((char *) cf->ctx + cmd-> conf);

                if ( confp) {
                    conf = confp[ ngx_modules[i ]->ctx_index];
                }
            }
            
             /*配置的set函数*/
            rv = cmd-> set(cf , cmd, conf);

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

            if ( rv == NGX_CONF_ERROR ) {
                return NGX_ERROR;
            }

            ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                               ""%s" directive %s" , name-> data, rv );

            return NGX_ERROR;
        }
    }

    if (found) {
        ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                           ""%s" directive is not allowed here" , name->data);

        return NGX_ERROR;
    }

    ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                       "unknown directive "%s"" , name-> data);

    return NGX_ERROR ;

invalid:

    ngx_conf_log_error (NGX_LOG_EMERG, cf, 0,
                       "invalid number of arguments in "%s" directive",
                       name ->data);

    return NGX_ERROR ;
}

下面我们假设配置读取的是http{}块,则set函数即为ngx_http_block,函数首先给ngx_http_conf_ctx_t配置分配内存,然后递归调用ngx_conf_parse开始解析所有NGX_HTTP_MODULE模块中main_conf字段,解析中如果遇到location{}或者server{}块,则调用相应的set函数进行解析,整个http{}块共享同一个main_conf。如果一个字段可以同时出现在main_conf,srv_conf中,则这个字段会保存在srv_conf中,如果一个字段可以同时出现在main_conf,srv_conf,loc_conf中,则此配置必定保存在loc_conf中。配置的合并其实就是对那些可以出现在多个层的字段,如果内层没有赋值,则由外层向内层赋值过程。

static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd , void *conf)
{
    char                        *rv;
    ngx_uint_t                   mi , m, s;
    ngx_conf_t                   pcf ;
    ngx_http_module_t           *module;
    ngx_http_conf_ctx_t         *ctx;
    ngx_http_core_loc_conf_t    *clcf;
    ngx_http_core_srv_conf_t   **cscfp;
    ngx_http_core_main_conf_t   *cmcf;
    
    /*ngx_http_ctx_t分配内存*/
    ctx = ngx_pcalloc (cf-> pool, sizeof(ngx_http_conf_ctx_t ));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }
    
    /*conf指针即ngx_conf_handler中取出的ngx_http_ctx_t指针的地址,将分配的内存赋值,相当于(((void **) cf->ctx)[ngx_modules[i]->index]) = ctx*/
    *(ngx_http_conf_ctx_t **) conf = ctx;


    /*设置HTTP模块相关配置的索引地址*/
    ngx_http_max_module = 0 ;
    for (m = 0 ; ngx_modules[m]; m ++) {
        if ( ngx_modules[m ]->type != NGX_HTTP_MODULE) {
            continue;
        }

        ngx_modules [m]-> ctx_index = ngx_http_max_module ++;
    }


    /* main_conf整个http{}块共享同一个main_conf */
    ctx->main_conf = ngx_pcalloc(cf->pool ,
                                 sizeof(void *) * ngx_http_max_module );
    if (ctx-> main_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    /*
     * http{}块没有srv_conf,合并时使用
     */
    ctx->srv_conf = ngx_pcalloc(cf->pool , sizeof (void *) * ngx_http_max_module);
    if (ctx-> srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    /*
     * http{}没有loc_conf,合并时使用
     */

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

    /*
     * create the main_conf's, the null srv_conf's, and the null loc_conf's
     * of the all http modules
     */

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

        module = ngx_modules[m]->ctx ;
        mi = ngx_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;
            }
        }
    }

    /*
     *保存原来上下文配置,cf->ctx赋值为ngx_http_ctx_t指针,后面要开始递归解析http{}块,在后面的我们会看到很多这样的操作
     */
    pcf = *cf;
    cf->ctx = ctx;

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

        module = ngx_modules[m]->ctx ;

        if ( module->preconfiguration ) {
            if ( module->preconfiguration (cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    /*
     *module_type和cmd_type赋值,进入ngx_conf_parse后解析NGX_HTTP_MODULE模块,并且NGX_HTTP_MAIN_CONF类型的配置
     */

    cf->module_type = NGX_HTTP_MODULE;
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
    rv = ngx_conf_parse (cf, NULL);

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

    /*
     * 上面解析完成后,所有的server{}列表保存在cmcf->servers中,然后开始合并server{}块,合并server{}块时,同时会开始合并location{}块
     */
    cmcf = ctx ->main_conf[ngx_http_core_module.ctx_index ];
    cscfp = cmcf ->servers.elts;

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

        module = ngx_modules[m]->ctx ;
        mi = ngx_modules[m]->ctx_index ;

        /* init http{} main_conf's */

        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;
        }
    }


    /* 创建location节点树, 把location链表转换成三叉树, 利于用户根据请求url取出相应的配置 */

    for (s = 0 ; s < cmcf->servers .nelts; s++) {

        clcf = cscfp[ s]->ctx->loc_conf [ngx_http_core_module.ctx_index];

        if ( ngx_http_init_locations(cf , cscfp[ s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

        if ( ngx_http_init_static_location_trees(cf , clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }


    if (ngx_http_init_phases(cf, cmcf ) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    if (ngx_http_init_headers_in_hash(cf, cmcf ) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


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

        module = ngx_modules[m]->ctx ;

        if ( module->postconfiguration ) {
            if ( module->postconfiguration (cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    /*
     * http{}'s cf->ctx was needed while the configuration merging
     * and in postconfiguration process
     */

    *cf = pcf;


    if (ngx_http_init_phase_handlers(cf, cmcf ) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


    /* optimize the lists of ports, addresses and server names */

    if (ngx_http_optimize_servers(cf, cmcf , cmcf-> ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK ;

failed:

    *cf = pcf;

    return rv ;
}

http模块解析完成后, 所有的server配置都会保存在cmcf->servers数组中, 每个server下的location会组成一个三叉树, 保存在相对应的server中ngx_http_core_loc_conf_t中static_locations. 服务器接收一个url请求后, 通过相应的地址和端口找到server配置, 通过三叉树找到相匹配的ngx_http_core_loc_conf_t配置, 进而获取到正确的配置信息.

原文地址:https://www.cnblogs.com/ourroad/p/4861096.html