- 微信公众号:郑尔多斯
- 关注「郑尔多斯」公众号 ,回复「领取资源」,获取IT资源500G干货。
升职加薪、当上总经理、出任CEO、迎娶白富美、走上人生巅峰!想想还有点小激动- 关注可了解更多的
Nginx知识。任何问题或建议,请公众号留言;
关注公众号,有趣有内涵的文章第一时间送达!
前言
前面的文章已经从源码级别将配置的解析过程说的很清楚了,本文我们学习一下nginx的http配置的merge过程。我们知道nginx的http配置结构体是一个非常复杂的东东,同样的指令可能出现在不同的位置,比如root指令,既可以出现在http的main级别,也可以出现在server,location,if 等上下文中,那么当一个请求到来的时候,nginx会使用哪一个上下文中的配置结果呢?这就牵涉到了http配置的merge过程。
背景
首先我们了解下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_conf和merge_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 结构体的一些字段进行初始化。
下面就是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 *module, 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;
// 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;
}
这个函数可以分为三部分来分析,第一部分,将main,server级别的配置合并起来。第二部分将server,location级别的配置合并起来。第三部分就是location和内嵌location的合并。
1) main和server级别级别的merge
这里的ctx_index是ngx_http_core_module在HTTP模块中的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的合并
// 这是第二层循环。对于每一个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 *module, 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;
// 如果没有内嵌的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;
}
四、总结
整个merge的过程可以总结如下:
第一层循环:遍历所有的 NGX_HTTP_MODULE 模块,对所有module进行调用ngx_http_merge_servers()函数,我们以 ngx_http_core_module 处理
第二层循环:该层循环在 ngx_http_merge_servers()函数内部,遍历所有的 server 块,逐个进行处理
1) 调用 ngx_http_core_module 的 create_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指令,可以放到main,server上下文中。从源码中可以看出来,client_header_timeout指令的配置数据是存储到所在级别的 ctx->srv_conf[ngx_http_core_module.ctx_index]中的(因为client_header_timeout的配置指令中offset为 NGX_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中。
其实将main和server合并的过程吧:只是针对client_header_timeout这种指令的,因为他们只能出现在main, server级别,并且保存在对应层级的 ctx_srv_conf[ctx_index]中。上面的合并函数传递的参数都是各个层级的 srv_conf[ctx_index].
五、参考
blog.csdn.net/weiwangchao…
喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达