最开始一直无法理解 在解析HTTP main上下文时,ctx->srv_conf
和ctx->loc_conf
有什么用处,其实仔细一想,也没啥用处,也就是临时存储在HTTP main阶段使用的指令解析结果,到最后反正会merge到下面一层的ctx->srv_conf
或者ctx->loc_conf
。
同样,HTTP server上下文时的ctx->loc_conf
也什么没用,暂存一些指令的结果而已,最后还是会merge到location 上下文的ctx->loc_conf
。
个人认为,Nginx的配置结构非常复杂,比Nginx其他模块的都复杂,想要彻底搞清楚,还需要下功夫。下面是个人的一些理解。
if (cmd->type & NGX_DIRECT_CONF) {
/*"daemon" "worker_processes"等指令为NGX_DIRECT_CONF指令
*其实也是NGX_MAIN_CONF指令,但是先判断了NGX_DIRECT_CONF所以走这个分支
*/
conf = ((void **) cf->ctx)[ngx_modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
/*例如"http" "mail" "events" 等指令为 NGX_MAIN_CONF 指令*/
conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);
} else if (cf->ctx) {
/*这些指令都是隶属于上层指令之下的指令,比如HTTP的server指令等*/
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
printf("-- else conf:%p\n", conf);
}
}
NGX_DIRECT_CONF
和NGX_MAIN_CONF
表示是配置文件的最外层指令,不同点在于DIRECT
和NGX_MAIN_CONF
配置的内存操作有差异。
cf->ctx
都是在 函数 ngx_init_cycle
中通过cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *))
获取得到的内存。
所以 DIRECT
类型 指令 获取的 conf 是 cycle->conf_ctx[index]
,而 MAIN
类型指令获取的 conf 是&cycle->conf_ctx[index]
。
对于DIRECT
指令,获取到的conf是自己create_conf
返回的内存。 例如 ngx_core_module_create_conf、ngx_regex_create_conf
等core module的create_conf注册函数,所以 cmd->set
中直接使用这个conf就行了。
对于MAIN
指令,conf取得是 cycle->conf_ctx[index]
的地址而不是里面的值,其实纯MAIN指令,没有create_conf指令,cf->ctx[index]
取出来也是NULL,所以取conf = &cf->ctx[index]
的目的,就是取二级指针,然后在cmd->set
函数中,执行类似*conf = malloc(..)
的操作,相当于对cf->ctx[index]
进行赋值。
话句话说,MAIN指令,其ctx不可能由create_conf
简单的创建出来或者压根不需要在create_conf
阶段创建内存,所以需要在cmd->set
中自行处理。
至于第三个if分支,处理的是即非MAIN
和非DIRECT
指令,下面用解析 HTTP块 指令来描述其作用。
以解析HTTP为例,解析函数是ngx_http_block
,而其是MAIN
类型的指令,所以 第三个参数conf
其实是&cycle->conf_ctx->ctx[index]
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_conf_ctx_t *ctx;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_http_conf_ctx_t **) conf = ctx;
}
我们看到了函数一开始就去申请内存,对 conf 代表的地址进行赋值。
这样cycle->conf_ctx[ngx_http_module.index]
就有值了,是ngx_http_conf_ctx_t
类型的内存
ngx_http_conf_ctx_t
有 三个 类型的conf成员组成:
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf;
} ngx_http_conf_ctx_t;
对各个成员申请内存,可见,每个 module 都可以有自己的 main_conf、srv_conf、loc_conf。因为每个module不同的指令,都有其想要控制的作用域。
ctx->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
初始化配置结构内存
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
mi = ngx_modules[m]->ctx_index;
if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_loc_conf) {
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
不同HTTP模块通过create_xx_conf
获得的返回值类型不一样。例如对于http core module来说,create_loc_conf
返回的是ngx_http_core_loc_conf_t
类型;对于http proxy module来说,返回的是ngx_http_proxy_loc_conf_t
类型。所以自己添加命令或者从现有命令中取配置时,需要注意处理对应的数据结构。
接下去就是递归解析里面的命令了,我们以解析到 client_header_timeout
为例
http {
client_header_timeout 10;
}
static ngx_command_t ngx_http_core_commands[] = {
......
{ 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 },
......
}
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF
表示支持配置的作用域,可见不支持在location块中配置。
我们回到文章开始,来看看如何处理在HTTP main作用下client_header_timeout
这个命令的。
......
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
printf("-- else conf:%p\n", conf);
}
cf->ctx
是在ngx_http_block
函数中开辟的ngx_http_conf_ctx_t
类型的数据结构。
cf->ctx + cmd->conf
对于client_header_timeout
指令而言,就是cf->ctx + NGX_HTTP_SRV_CONF_OFFSET
,实际上获取到的是ngx_http_conf_ctx_t->srv_conf
,对于HTTP模块,confp不为空(main_conf、srv_conf、loc_conf都开辟内存了),所以 conf = confp[ngx_modules[i]->ctx_index];
取到的conf,就是ngx_http_core_srv_conf_t
类型的内存,它是在 初始化配置结构内存 时create_srv_conf
函数指针所代表的ngx_http_core_create_srv_conf
函数中开辟的。
如果出现这种情况:
http {
client_header_timeout 10;
server {
client_header_timeout 5;
}
}
http main上下文中出现的client_header_timeout
命令上面说了,其值存在下面ctx2的内存中:
ngx_http_conf_ctx_t *ctx1 = cycle->conf_ctx[ngx_http_module.index];
ngx_http_core_srv_conf_t * ctx2 = ctx1->srv_conf[ngx_http_core_module.ctx_index]
在http server上下文中出现的client_header_timeout
,其存储位置则不一样了
ngx_http_conf_ctx_t *ctx3 = //ngx_http_core_server中新开辟的内存;
ngx_http_core_srv_conf_t * ctx4 = ctx3->srv_conf[ngx_http_core_module.ctx_index];
上面可以看到,ctx3是在函数内部动态申请的,ctx4是我们client_header_timeout 5;
指令存储的位置。
可见,同一个命令2个不同上下文,其存储的内存也是不同的。这就要求在解析完成的最后阶段,需要抉择这个server 块 到底需要按照哪个命令的结果,到底是10还是5。这个操作就是merge操作。我们先看看ctx3和ctx4保存在哪里。
在解析server块的时候,我们有如下操作:
/*cscf就是 ctx4 , ctx 就是 ctx3*/
cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
cscf->ctx = ctx;
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = ngx_array_push(&cmcf->servers);
if (cscfp == NULL) {
return NGX_CONF_ERROR;
}
/*把ctx4存入到main_conf中*/
*cscfp = cscf;
说白了,ctx3 是在函数内部动态申请的一块内存,总得保存在某个地方吧,否则就内存泄漏了,于是执行了cscf->ctx = ctx;
,即把ctx3保存在ctx4中,然而ctx4本身也是函数内部动态申请的,所以ctx4就保存在cmcf->servers
中,ctx->main_conf = 上一级的main_conf,上一级的main_conf就能从cycle->conf_ctx[index]
中取到了。
所以merge操作如下:
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;
/*每个module都可以有自己的main_conf*/
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
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;
}
}
/*进行merge, cf->ctx 就是 ctx1*/
rv = ngx_http_merge_servers(cf, cmcf, module, mi);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
merge总得有2个对象吧,一个current 一个是prev,把prev按照自己的逻辑写到current中。
显然,对于下面这种类型的配置方式,prev是http main上下文保存的内存,current是http server 上下文的内存。
http {
client_header_timeout 10;
server {
client_header_timeout 5;
}
}
prev就是 ctx2,current就是 ctx4。
怎么获取到ctx2呢,其实上面说了:
ngx_http_conf_ctx_t *ctx1 = cycle->conf_ctx[ngx_http_module.index];
ngx_http_core_srv_conf_t * ctx2 = ctx1->srv_conf[ngx_http_core_module.ctx_index]
而ctx4,就需要从 cmcf 中的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)
{
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 = cmcf->servers.elts;
ctx = (ngx_http_conf_ctx_t *) cf->ctx;
saved = *ctx;
rv = NGX_CONF_OK;
for (s = 0; s < cmcf->servers.nelts; s++) {
/* merge the server{}s' srv_conf's */
/*cscfp[s]就是ctx4, cscfp[s]->ctx就是ctx3
* cscfp[s]->ctx->srv_conf[ctx_index]和cscfp[s]的效果是等价的。所以ctx4是找到了。
* 现在找ctx1,上面说过,ctx1就是 cf->ctx 中 的 srv_conf[ctx_index]。
*
*/
ctx->srv_conf = cscfp[s]->ctx->srv_conf;
if (module->merge_srv_conf) {
/*入参2是上层配置,即prev,入参2是里层配置,即current*/
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;
}
}
......
}
failed:
*ctx = saved;
return rv;
}