- 微信公众号:郑尔多斯
- 关注「郑尔多斯」公众号 ,回复「领取资源」,获取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;
}
}
}
-
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
块,逐个进行处理
- 调用
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]
.
五、参考
https://blog.csdn.net/weiwangchao_/article/details/46379999
喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达 ![郑尔多斯](https://mmbiz.qpic.cn/mmbiz_jpg/rLxDDpWK43nImp1Fs8OzHkyYiaUnvXvGibjiazibNIYPsHOibKib0B1ARQicuZ2X01jdV4YuYUGfyFyeKwxp38MEPsGpg/0?wx_fmt=jpeg)