10.2.4 不同级别配置项的合并

考虑到HTTP模块可能需要合并不同级别的同名配置项,因此,HTTP框架为ngx_http_module_t接口提供了merge_srv_conf方法,用于合并main级别与srv级别的server相关的配置项,同时,它还提供了merge_loc_conf方法,用于合并main级别、srv级别、loc级别的location相关的配置项。当然,如果不存在合并不同级别配置项的场景,那么可以不实现这两个方法。下面仍然以10.1节的配置文件为例,展示了不同级别的配置项结构体是如何合并的,如图10-8所示。

10.2.4 不同级别配置项的合并 - 图1

图 10-8 main、srv、loc级别的同名配置项合并前的内存示意图

图10-8以第5个HTTP模块(通常是ngx_http_static_module模块)为例,展示了在解析完http块、server A块、location L1块后是如何合并配置项的。第5个HTTP模块在解析这3个配置块时,create_loc_conf_t方法被调用了3次,产生了3个结构体,分别存放了main、srv、loc级别的location相关的配置项,这时可以合并为location L1相关的配置结构体;create_srv_conf_t方法被调用了两次,产生了两个结构体,分别存放了main、srv级别的配置项,这时也可以合并为server A块相关的配置结构体。

合并配置项可能不太容易理解,下面我们就在代码实现层面上再做个简要的介绍,同时也对10.2.2节和10.2.3节的内容做一个回顾。这个合并操作是在ngx_http_merge_servers方法下进行的,先来简单地看看它是怎么被调用的:


/*cmcf是ngx_http_core_module在http块下的全局配置结构体,在10.2.2节介绍过它的servers成

员,这是一个动态数组,它保存着所有ngx_http_core_srv_conf_t的指针,从而关联了所有的server块*/

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

//ngx_modules数组中包含所有的Nginx模块

for(m=0;ngx_modules[m];m++){

//遍历所有的HTTP模块

if(ngx_modules[m]->type!=NGX_HTTP_MODULE){

continue;

}

/ngx_modules[m]是一个ngx_module_t模块结构体,它的ctx成员对于HTTP模块来说是ngx_http_module_t接口/

ngx_http_module_t*module=ngx_modules[m]->ctx;

//ctx_index是这个HTTP模块在所有HTTP模块中的序号

mi=ngx_modules[m]->ctx_index;

//调用ngx_http_merge_servers方法合并ngx_modules[m]模块

rv=ngx_http_merge_servers(cf,cmcf,module,mi);

}


ngx_http_merge_servers方法不只是合并了server相关的配置项,它同时也会合并location相关的配置项,下面再来看看它的实现,代码如下。


static charngx_http_merge_servers(ngx_conf_tcf,ngx_http_core_main_conf_tcmcf,ngx_http_module_tmodule,ngx_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;

/从ngx_http_core_main_conf_t的servers动态数组中可以获取所有的ngx_http_core_srv_conf_t结构体/

cscfp=cmcf->servers.elts;

//注意,这个ctx是在http{}块下的全局ngx_http_conf_ctx_t结构体

ctx=(ngx_http_conf_ctx_t*)cf->ctx;

saved=*ctx;

//遍历所有的server块下对应的ngx_http_core_srv_conf_t结构体

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

/srv_conf将指向所有的HTTP模块产生的server相关的srv级别配置结构体/

ctx->srv_conf=cscfp[s]->ctx->srv_conf;

//如果当前HTTP模块实现了merge_srv_conf,则再调用合并方法

if(module->merge_srv_conf){

/*注意,在这里合并配置项时,saved.srv_conf[ctx_index]参数是当前HTTP模块在http{}

块下由create_srv_conf方法创建的结构体,而cscfp[s]->ctx->srv_conf[ctx_index]参数则是在server{}

块下由create_srv_conf方法创建的结构体*/

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

}

//如果当前HTTP模块实现了merge_srv_conf,则再调用合并方法

if(module->merge_loc_conf){

/cscfp[s]->ctx->loc_conf这个动态数组中的成员都是由server{}块下所有HTTP模块的create_loc_conf方法创建的结构体指针/

ctx->loc_conf=cscfp[s]->ctx->loc_conf;

/首先将http{}块下main级别与server{}块下srv级别的location相关的结构体合并/

rv=module->merge_loc_conf(cf,saved.loc_conf[ctx_index],cscfp[s]->ctx->loc_conf[ctx_index]);

/clcf是server块下ngx_http_core_module模块使用create_loc_conf方法产生的ngx_http_core_loc_conf_t结构体,在10.2.3节中曾经说过,它的locations成员将以双向链表的形式关联到所有当前server{}块下的location块/

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

/调用ngx_http_merge_locations方法,将server{}块与其所包含的location{}块下的结构体进行合并/

rv=ngx_http_merge_locations(cf,clcf->locations,

cscfp[s]->ctx->loc_conf,

module,ctx_index);

}

}

}


ngx_http_merge_locations方法负责合并location相关的配置项,上面已经将main级别与srv级别做过合并,接下来再次将srv级别与loc级别做合并。每个server块ngx_http_core_loc_conf_t中的locations双向链表会包含所属的全部location块,遍历它以合并srv、loc级别配置项,如下所示。


static char*

ngx_http_merge_locations(ngx_conf_tcf,ngx_queue_tlocations,

void*loc_conf,ngx_http_module_tmodule,ngx_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;

/如果locations链表为空,也就是说,当前server块下没有location块,则立刻返回/

if(locations==NULL){

return NGX_CONF_OK;

}

ctx=(ngx_http_conf_ctx_t*)cf->ctx;

saved=*ctx;

//遍历locations双向链表

for(q=ngx_queue_head(locations);

q!=ngx_queue_sentinel(locations);

q=ngx_queue_next(q))

{

lq=(ngx_http_location_queue_t*)q;

/在10.2.3节中曾经讲过,如果location后的匹配字符串不依靠Nginx自定义的通配符就可以完全匹配的话,则exact指向当前location对应的ngx_http_core_loc_conf_t结构体,否则使用inclusive指向该结构体,且exact的优先级高于inclusive/

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

/clcf->loc_conf这个指针数组里保存着当前location下所有HTTP模块使用create_loc_conf方法生成的结构体的指针/

ctx->loc_conf=clcf->loc_conf;

//调用merge_loc_conf方法合并srv、loc级别配置项

rv=module->merge_loc_conf(cf,loc_conf[ctx_index],

clcf->loc_conf[ctx_index]);

/注意,因为location{}中可以继续嵌套location{}配置块,所以是可以继续合并的。在10.1节的例子中没有location嵌套,10.2.3节的例子是体现出嵌套关系的,可以对照着图10-5来理解/

rv=ngx_http_merge_locations(cf,clcf->locations,clcf->loc_conf,module,ctx_index);

}

*ctx=saved;

return NGX_CONF_OK;

}


在针对每个HTTP模块循环调用ngx_http_merge_servers方法后,就可以完成所有的合并配置项工作了。