- 微信公众号:郑尔多斯
- 关注「郑尔多斯」公众号 ,回复「领取资源」,获取IT资源500G干货。
升职加薪、当上总经理、出任CEO、迎娶白富美、走上人生巅峰!想一想还有点小激动- 关注可了解更多的
Nginx
知识。任何问题或建议,请公众号留言;
关注公众号,有趣有内涵的文章第一时间送达!
前面的文章已经从源码级别将配置的解析过程说的很清楚了,本文咱们学习一下nginx
的http
配置的merge
过程。咱们知道nginx
的http
配置结构体是一个很是复杂的东东,一样的指令可能出如今不一样的位置,好比root
指令,既能够出如今http
的main
级别,也能够出如今server
,location
,if
等上下文中,那么当一个请求到来的时候,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_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
结构体的一些字段进行初始化。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 *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
的合并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 *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;
}
复制代码
4、总结
整个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]
.
5、参考
blog.csdn.net/weiwangchao…