合并http配置项

  • 微信公众号:郑尔多斯
  • 关注「郑尔多斯」公众号 ,回复「领取资源」,获取IT资源500G干货。
    升职加薪、当上总经理、出任CEO、迎娶白富美、走上人生巅峰!想一想还有点小激动
  • 关注可了解更多的Nginx知识。任何问题或建议,请公众号留言;
    关注公众号,有趣有内涵的文章第一时间送达!

前言

前面的文章已经从源码级别将配置的解析过程说的很清楚了,本文咱们学习一下nginxhttp配置的merge过程。咱们知道nginxhttp配置结构体是一个很是复杂的东东,一样的指令可能出如今不一样的位置,好比root指令,既能够出如今httpmain级别,也能够出如今serverlocationif 等上下文中,那么当一个请求到来的时候,nginx会使用哪个上下文中的配置结果呢?这就牵涉到了http配置的merge过程。nginx

背景

首先咱们了解下merge 的背景:数组

  • 所谓merge 操做,就是合并内外层的配置。大致原则是:若是内层没有配置,那么之外层为准,若是都没有配置,那么就用默认值;
  • NGX_CORE_MODULE 模块的ctx(ngx_core_module_t)是没有 merge 操做的,因此像 http 块这一层的配置是不须要和上一层去merge 的,想一想也明白为何,http哪来的上一层呢?
  • NGX_HTTP_MODULE模块的 ctx(ngx_http_module_t)是有 merge 操做的,可是仅仅有 merge_srv_confmerge_loc_conf,同理对 main 层不须要 merge
    -merge 操做发生的时机是在 ngx_http_block函数中(即 http 块解析函数),在递归调用 ngx_conf_parse 以后。这是为了让http 块以内全部的指令都解析结束,而后再去作 merge 操做;
  • 不一样层级块的逻辑关系,基本上都是放在 ngx_http_core_module 这个模块的不一样级别的 conf 中,在 merge中会频繁用到。

源码分析

http配置的merge过程是在ngx_http_block()函数中实现,以下:服务器

// 哈哈,扯淡的东西  cmcf = core main conf  我猜的。 
// cscf = core server conf, clcf = core location conf
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

    for (m = 0; ngx_modules[m]; m++) {
// 只对http module才存在merge操做
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
        module = ngx_modules[m]->ctx;
// mi === module index 模块索引的意思,表示当前module在该类型中的索引
        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;
        }
    }
复制代码

当执行这部分代码的时候,nginx已经解析完了http的全部配置项,因此才可以实现merge过程。
从代码中能够看出来,会先调用每一个HTTP模块的 init_main_conf 函数,下面的是 ngx_http_core_module 模块的钩子函数,以下:微信

static char *
ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
{
    ngx_http_core_main_conf_t *cmcf = conf;

    if (cmcf->server_names_hash_max_size == NGX_CONF_UNSET_UINT) {
        cmcf->server_names_hash_max_size = 512;
    }

    if (cmcf->server_names_hash_bucket_size == NGX_CONF_UNSET_UINT) {
        cmcf->server_names_hash_bucket_size = ngx_cacheline_size;
    }

    cmcf->server_names_hash_bucket_size =
            ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size);

    if (cmcf->variables_hash_max_size == NGX_CONF_UNSET_UINT) {
        cmcf->variables_hash_max_size = 512;
    }

    if (cmcf->variables_hash_bucket_size == NGX_CONF_UNSET_UINT) {
        cmcf->variables_hash_bucket_size = 64;
    }

    cmcf->variables_hash_bucket_size =
               ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);

    if (cmcf->ncaptures) {
        cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
    }
    return NGX_CONF_OK;
}
复制代码

上面的函数没有什么复杂的地方,就是对 ngx_http_core_,main_conf_t 结构体的一些字段进行初始化。app

下面就是merge的过程了,咱们对代码精简一下,以下:函数

    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
        module = ngx_modules[m]->ctx;
// mi === module index 模块索引的意思,表示当前module在该类型中的索引
        mi = ngx_modules[m]->ctx_index;

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

这段代码的总体逻辑仍是比较简单的,遍历全部的HTTP模块,而后对每一个module都调用 ngx_http_merge_servers()函数,因此真正的merge逻辑是在这个函数中的。
源码分析

内存布局
内存布局
/*
① cf 是代入的参数,可是咱们真正关心的仍是 cf->ctx,这个时候它其实就是 http 级别的三元组(在ngx_http_block函数中赋值)
② cmcf 这个是 http 块的 ngx_http_core_module 的 main_conf 结构,该结构是全局惟一的,由于不管server级别的ctx仍是location级别的ctx,他们的main_conf都指向了http全局的main_conf
③ module 是个循环获取的,表明当前遍历到的HTTP module的ctx
④ mi 就是当前模块在 NGX_HTTP_MODULE 模块中的 index

这个函数就实现了当前被遍历到的http module的server以及location的merge操做
*/

static char *
ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_http_module_t *modulengx_uint_t ctx_index)

{
    char                        *rv;
    ngx_uint_t                   s;
    ngx_http_conf_ctx_t         *ctx, saved;
    ngx_http_core_loc_conf_t    *clcf;
    ngx_http_core_srv_conf_t   **cscfp;

// cscfp: core server conf pointer 指向保存全部server数组的指针
    cscfp = cmcf->servers.elts;
    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
/* 
这里作了一个保存的操做,由于在下面的代码中要改变 ctx 中的值,
而且同时使用原始的 ctx。在最后又经过saved变量复原了ctx的值
*/

    saved = *ctx;
    rv = NGX_CONF_OK;
/*
这是第二层循环。对于每个HTTP module,都会遍历全部的server模块。
为何要再循环一次呢?我是这么理解的:
http {
            instruction_A   value_main_A;
            server {
                # server_1
                instruction_A  value_srv_1;
            }

            server {
                   #server_2
                  instruction_A  value_srv_2
            }
}
上述的instruction既出如今了http  main 级别,又出如今了server级别。而且http配置中有多个server,因此要遍历全部的server,将每一个server的配置都和main的配置进行合并。
*/

    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_srv_conf) {
            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
                                        cscfp[s]->ctx->srv_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
                                        cscfp[s]->ctx->loc_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
            /* merge the locations{}' loc_conf's */
            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
            rv = ngx_http_merge_locations(cf, clcf->locations,
                                          cscfp[s]->ctx->loc_conf,
                                          module, ctx_index);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
failed:
    *ctx = saved;
    return rv;
}
复制代码
简明示例布局
简明示例布局

这个函数能够分为三部分来分析,第一部分,将mainserver级别的配置合并起来。第二部分将serverlocation级别的配置合并起来。第三部分就是location和内嵌location的合并。
1) mainserver级别级别的merge
这里的ctx_indexngx_http_core_moduleHTTP模块中的index布局

 for (s = 0; s < cmcf->servers.nelts; s++) {
        /* merge the server{}s' srv_conf's */
/* 改变 cf->ctx 的 srv_conf,换成当前被遍历到的 server 块对应的 srv_conf。*/
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_srv_conf) {
/* 这里就很明朗了,saved 就是http main级别 的cf->ctx 的内容,那么它就是 http main级别块的ctx三元组了,第二个参数也就是当前被遍历到的HTTP module在 http main级别的ctx->srv_conf数组中对应的 srv_conf,也即上图中的http_srv_A结构体,也即parent

cscfp[s] 表明对应的当前遍历的server,而它的 ctx 也就是在解析那个 server 块的时候建立的三元组。因此第三个参数就是上图中的server_srv_A,也即child

http{
      // main级别
       root /data0/w3;
       server {
          // server_A
       }

       server {
            // server_B
      }
}
咱们以上面的配置为例来讲明:这一部分代码会遍历全部的server块,也就是会逐个遍历server_A和server_B。
为何要遍历全部的server块呢?由于要把main级别的配置同步到全部的server块中。
经过这两个参数就能够将main级别的配置项和server级别的配置项合并了.
综上所述:main和server的merge其实就是http main级别ctx->srv_conf下的结构体和server级别的ctx->srv_conf下的结构体的合并。
和loc_conf下的结构体没有任何关系。
*/

            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
                                        cscfp[s]->ctx->srv_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
}
复制代码

2) server级别和location级别配置项的合并
server级别和location级别配置项合并的原理和上面的原理基本相同。都是逐个遍历的。学习

// 这是第二层循环。对于每个HTTP module,都会遍历全部的server模块
    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
/*
如下图为例
        http{
               // main级别
                root /data0/w3;
                server {
                     // server_A
                     location balabala{
                           // location_C
                     }

                   location cilili {
                           // location_D
                    }
               }

               server {
                  // server_B
              }
           }

merge_loc_conf()的第二个参数是当前遍历到的HTTP module在 main 级别的 ctx->loc_conf数组中的配置,即上图中的http_loc_A, 是parent级别的location配置。
第三个参数就是 server_A 级别的 ctx->loc_conf[ngx_http_core_module.ctx_index],即上图中的server_loc_A, 是child级别的location配置。
*/

            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
                                        cscfp[s]->ctx->loc_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
复制代码

3) location和内嵌location的合并ui

// 这是第二层循环。对于每个HTTP module,都会遍历全部的server模块
    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;

            /* merge the locations{}' loc_conf's */
// clcf 是 server_A的 ctx->loc_conf[ngx_http_core_module.ctx_index]
            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
// 第二个参数: clcf->locations 在server_A这个server块下面的全部location组成的queue.
// 第三个参数: server_A级别的 ctx->loc_conf 数组。
// 第四个参数:  由于此时是合并ngx_http_core_module的配置项,因此module参数指的是ngx_http_core_module的module_ctx模块上下文。
// 第五个参数: 由于此时是合并ngx_http_core_module的配置项,因此ctx_index是ngx_http_core_module在全部HTTP module中的ctx_index
            rv = ngx_http_merge_locations(cf, clcf->locations,
                                          cscfp[s]->ctx->loc_conf,
                                          module, ctx_index);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
复制代码

这里牵涉到了另外一个函数ngx_http_merge_locations(),以下:

// 第二个参数: locations : clcf->locations 在server_A这个server块下面的全部location组成的queue.
// 第三个参数: loc_conf : server_A级别的 ctx->loc_conf 数组。
// 第四个参数:  module : 由于此时是合并ngx_http_core_module的配置项,因此module参数指的是ngx_http_core_module的module_ctx模块上下文。
// 第五个参数: ctx_index : 由于此时是合并ngx_http_core_module的配置项,因此ctx_index是ngx_http_core_module在全部HTTP module中的ctx_index
static char *
ngx_http_merge_locations(ngx_conf_t *cf, ngx_queue_t *locations,
    void **loc_conf, ngx_http_module_t *modulengx_uint_t ctx_index)

{
    char                       *rv;
    ngx_queue_t                *q;
    ngx_http_conf_ctx_t        *ctx, saved;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_location_queue_t  *lq;
 // 若是没有内嵌的location,则该函数直接返回
    if (locations == NULL) {
        return NGX_CONF_OK;
    }
 /* 这里代码看似和 ngx_http_merge_servers 相似,可是差异在于,这个时候 cf->ctx 内
 * 相对应的 srv_conf 和 loc_conf 内容已经被改变为外层的对应 conf。在
 * ngx_http_merge_servers 中的相关代码咱们已经分析过了,在这个函数中一样有相似的代码。
*/

    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
    saved = *ctx;

    for (q = ngx_queue_head(locations);
         q != ngx_queue_sentinel(locations);
         q = ngx_queue_next(q))
    {
/* 遍历当前server下面的全部 location,逐个 merge。*/
        lq = (ngx_http_location_queue_t *) q;
        clcf = lq->exact ? lq->exact : lq->inclusive;
/* 改变 cf->ctx 的 loc_conf,换成当前 server 块对应的 loc_conf。*/
        ctx->loc_conf = clcf->loc_conf;
/*
http{
     server {
          // server_A
           location B {
               // location_B
            }

          location C{
                 // location_C
           }

         location D{
              // location_D
          }
     }
}
   下面的 merge_loc_conf() 的参数上文已经分析过了。这不过这里是location和内嵌location的合并,咱们再分析一下:
第二个参数: loc_conf[ctx_index] = server_A级别的 ctx->loc_conf[ngx_http_core_module.ctx_index],也就是parent级别的数据。
第三个参数:clcf->loc_conf[ctx_index] = location_B级别的 ctx->loc_conf[ngx_http_core_module.ctx_index],也就是child级别的数据。
*/

        rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
                                    clcf->loc_conf[ctx_index]);
/* server 的时候,它是对应 server 块的 loc_conf 数组。在下面的代码中,递归调用
 * ngx_http_merge_locations,代入的 loc_conf 就是这一层块的 loc_conf 数组,因此这里的
 * loc_conf 其实就表明外层块的 loc_conf 数组。而 clcf 是很明显的,是由 locations 队列
 * 遍历产生的,也就是表明当前的 location 块。因此这里,第二个参数是 parent,第三个参
 * 数是 child。
 */

        if (rv != NGX_CONF_OK) {
            return rv;
        }
 // 嵌套的location
/* 这里递归调用了 ngx_http_merge_locations,嵌套 location 的 merge 操做也能够成功解决
 * 了,惟一值得注意的就是那些代入的参数,由于进入一层,因此对应的 locations 和
 * loc_conf 也更进了一层。
*/

        rv = ngx_http_merge_locations(cf, clcf->locations, clcf->loc_conf,
                                      module, ctx_index);
        if (rv != NGX_CONF_OK) {
            return rv;
        } 
    }
    *ctx = saved;
    return NGX_CONF_OK;
}
复制代码

4、总结
整个merge的过程能够总结以下:
第一层循环:遍历全部的 NGX_HTTP_MODULE 模块,对全部module进行调用ngx_http_merge_servers()函数,咱们以 ngx_http_core_module 处理
第二层循环:该层循环在 ngx_http_merge_servers()函数内部,遍历全部的 server 块,逐个进行处理
1) 调用 ngx_http_core_modulecreate_srv_conf() 函数对 main 级别的 srv_conf[ctx_index]结构体和各个server级别 srv_conf[ctx_index] 配置结构体合并。
为何这样作呢?咱们以client_header_timeout指令为例:

Defines a timeout for reading client request header. If a client does not transmit the entire header within this time, the request is terminated with the 408 (Request Time-out) error.

这个指令的做用:该指令决定了nginx接收request header的最长时间。若是服务器在指定的时间内没有接收到完整的request header,那么这个HTTP请求就会返回408错误(该错误表示Request Time-out请求超时)。

    { 
      ngx_string("client_header_timeout"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_core_srv_conf_t, client_header_timeout),
      NULL 
    }
复制代码

client_header_timeout指令,能够放到mainserver上下文中。从源码中能够看出来,client_header_timeout指令的配置数据是存储到所在级别的 ctx->srv_conf[ngx_http_core_module.ctx_index]中的(由于client_header_timeout的配置指令中offsetNGX_HTTP_SRV_CONF_OFFSET)。

   http {
       client_header_timeout 10s;
       server server_A {
        }

       server server_B {
       client_header_timeout 5s;
     }
}
复制代码

main级别配置了client_header_timeout指令, 可是server_A 并无配置 client_header_timeout 指令,因此咱们要把 main 级别的配置合并到 server 级别。这样 server_A 就能够有本身的 client_header_timeout 配置了。
由于 client_header_timeout 能够出如今任何的 server 模块中,因此要遍历全部的 server,将出如今 main 中的配置项合并到各个server中。
其实将mainserver合并的过程吧:只是针对client_header_timeout这种指令的,由于他们只能出如今main, server级别,而且保存在对应层级的 ctx_srv_conf[ctx_index]中。上面的合并函数传递的参数都是各个层级的 srv_conf[ctx_index].

5、参考
blog.csdn.net/weiwangchao…



喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达
郑尔多斯
郑尔多斯
相关文章
相关标签/搜索