Nginx源码剖析--HTTP模块配置信息的merge


前言

考虑有如下形式的配置文件:

http{
   ....
   server {
       .....
       location {
           .....
       }
   }
}

根据前面的讲解,Nginx会为每个http块,server块和location块创建一个ngx_http_conf_ctx_t结构体。这个结构体主要是为了管理各个模块的main_conf,srv_conf,loc_conf。

我们知道,对于http块,Nginx会为每个模块创建他的main_conf,srv_conf,loc_conf;对与每个server块,Nginx会为每个模块创建srv_conf,loc_conf;对于每个location块,Nginx会为每个模块创建它的loc_conf。

按照朴素的想法,这种构建构建结构体的方式会导致很多冗余。比如在http块中,按道理应该只创建main_conf,而不必创建srv_conf和loc—_conf,而在server块中,只需要创建srv_conf,在location块中应该只创建loc_conf。这样才比较合理。但NGINX选择了这种冗余的创建方式,主要是基于灵活性的考虑:

  1. 可以在HTTP块中写srv类型和loc类型的配置项信息,在sever配置块中写loc类型的配置项信息。而不必限制一定要在server块中才能写srv类型的配置项信息,在location块中才能写loc类型的配置项信息。
  2. 根据前一篇博文中所述,由于server块对应的ngx_http_conf_ctx_t中的main_conf指向http块的main_conf数组,因此可以在server块中写main_conf类型的配置项。location同理。
  3. 由于可以在http块中写srv-conf配置项,server块中也可以写srv_conf配置项。所以相当于同一个配置项可以有多个值。然后我们可以选择使用哪个值。这就是merge的作用。

比如对于如下这样额配置:

http{
  server_name="wen";
  server {
      server_name="guang";
  }

}

对于srv_conf类型的配置项server_name的值,可以有两个选择。而merge过程就是决定选哪个作为最终的值。就是合并。

  1. 灵活性还来自下面这种写法:
http{
     pp=12;
     server {
       server_name="wen";
     }
     server {
        server_name="shan";
    }

}

对于srv_conf类型的配置项pp,为了让所有server块中的模块的srv_conf配置结构体的pp项都包含相同的值,不需要在每个server块里面都写pp=12,只需要在最外层写就可以了,然后merge过程会把这个pp配置项的值merge到所有模块的drv_conf中。

下面我们具体讲一下merge的过程。


merge过程的实现

merge过程的入口是ngx_http_merge_servers函数。

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

根据这个函数的参数,我们可以理解为是对一个模块-module的配置结构体的合并。主要包括以下三个方面的合并:

  1. 将module的所有server块下的srv_conf和外层http块下的srv_conf合并。当然,server块之间是不能合并的。
  2. 将module的http块下的loc_conf和这个server块的loc_conf合并。对每个server块都是如此。
  3. 将server块下的loc_conf和其直接下属的所有location块合并,以及各个location块下的所有location块的loc_conf和这个location块的loc_conf合并。

这里需要注意一下的是:我们说的server块下的location块是指server块下的直属location块。location块下的location块只的也是location的直属下的location块。

我们详细看一下代码,代码也是按照上面的三个阶段进行讲解:

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

        /* merge the server{}s' srv_conf'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;
            }
        }

循环所有的server块。将所有server块下当前module对应的srv_conf和http块中的srv_conf进行合并:

module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
                                        cscfp[s]->ctx->srv_conf[ctx_index]);

这个合并的动作是由模块自己实现的,因此行为是模块的提供者自己定义的。没有什么普遍性。

第二阶段就是将http块下的loc_conf和server块下的loc_conf合并:

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

这段代码执行之后,该server块对应的loc_conf就和外层的http块的loc_conf完成了合并。

第三个阶段就是将当前server块中的所有直属下属块和server块的loc_conf合并。由于第二阶段中server块的loc_conf和http块下的loc_conf已经合并了,所以http块的loc_conf也会作用于server下属的location块的loc_conf中。第三个阶段代码如下:

            /* 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);

显然,主要任务在ngx_http_merge_locations 函数中完成。

     ......
    for (q = ngx_queue_head(locations);
         q != ngx_queue_sentinel(locations);
         q = ngx_queue_next(q))
    {
        lq = (ngx_http_location_queue_t *) q;

        clcf = lq->exact ? lq->exact : lq->inclusive;
        ctx->loc_conf = clcf->loc_conf;

        rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
                                    clcf->loc_conf[ctx_index]);
        if (rv != NGX_CONF_OK) {
            return rv;
        }

        rv = ngx_http_merge_locations(cf, clcf->locations, clcf->loc_conf,
                                      module, ctx_index);
        if (rv != NGX_CONF_OK) {
            return rv;
        }
    }

这个函数的主体和ngx_http_merge_servers函数还是很相似的。

它会循环当前server下的所有直属location块,然后将这个server块下module的loc_conf分别和各个location块下该module的loc_conf合并。记住,各个层级上平行的location块之间是不发送合并的。

由于location块是允许嵌套location块的。因此我们还需要把这个location块和它直属下属的location块下该module对应的loc_conf合并,因此代码中会递归调用ngx_http_merge_locations


总结

到这里就介绍完了Nginx的合并过程。还是很简单的。概括一下大概就是,合并过程是自顶向下的,也就是说,根据层级嵌套,外层的配置项可能会和内层的配置项合并,但是同一个层级的配置项是不会发生合并的。

配置项的合并为配置文件的书写提供了更多的灵活性。一方面多个内层块可以共享共同外层的配置值,另一方面合并操作的自定义特性也提供了更多的可能性。还有就是配置项在配置文件中的书写位置限制更少。

猜你喜欢

转载自blog.csdn.net/swartz2015/article/details/78365143