本篇文章将具体介绍一下Nginx解析HTTP模块配置文件的流程。
核心模块ngx_http_module是进入HTTP模块解析的入口。该模块仅仅定义了一个ngx_command_t结构体,用于声明遇到http{}时需要进入的处理函数
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
//开始解析http模块相关的配置项
ngx_http_block,
0,
0,
NULL },
ngx_null_command
};
而ngx_http_core_module则定义了默认的配置文件的解析规则、create_XXX_conf生成的结构体的类型等。先说一下所有级别的配置项是如何在内存中关联起来的,ngx_http_core_module定义了三种配置数据结构ngx_http_core_main_conf_t、ngx_http_core_srv_t、ngx_http_core_loc_t。对于ngx_http_core_main_conf_t来说,它包含一个servers动态数组,用于指向所有由ngx_http_core_module创建的ngx_http_core_srv_conf_t数据结构。而ngx_http_core_srv_conf_t 数据结构中又包含了指向它上下文的指针;而ngx_http_core_loc_conf_t中有一个locations的双向循环链表,它可以将不同级别的属于location的配置项关联起来。借用《Nginx模块开发与架构解析》中的图:
这样的关系是ngx_http_core_module中定义的。但是由于ngx_http_conf_ctx的关联,可以找到所有模块的配置结构体。
http{}级别解析
所以当遇到http{}时,调用ngx_http_block函数,在这个函数中,将会调用ngx_http_module_t公共接口中的回调函数,其中关于配置项解析的处理流程是这样的:
1、创建配置上下文结构体ngx_http_conf_ctx_t,并为其包含的三个数组分配空间。
2、对于每一个HTTP模块,如果定义了create_main_conf函数,则调用它,将产生的结构体按照模块的序号加入到ngx_http_conf_ctx_t的main_conf数组中;如果定义了create_srv_conf函数,则调用它,将产生的结构体按照模块的序号加入到ngx_http_conf_ctx_t的srv_conf数组中;如果定义了create_loc_conf函数,则调用它,将产生的结构体按照模块的序号加入到ngx_http_conf_ctx_t的loc_conf数组中。
3、调用每个HTTP模块的preconfiguration函数。
4、调用ngx_conf_parse函数真正进行解析,该函数扫描配置文件,遇到模块的ngx_command_t中匹配的配置项时,调用其set函数,进行参数值的提取或者继续进行server级别和main级别配置块的解析。执行完这个函数后,各个配置结构体的成员的值已经填充完毕。
5、调用每个模块的init_main_conf函数。
6、调用merge_srv_conf和merge_loc_conf函数进行配置项的合并。合并包括http与srv级别的server有关的配置项的合并、http与server级别的location有关的配置项合并、以及server与location级别的location相关的配置项合并以及location级别嵌套的location相关配置项的合并(比较绕。。。)。我们在上面介绍了,由于它们以一种比较复杂的关系关联起来,可以很方便的将不同级别的配置结构体做合并操作。
ngx_http_core_module定义了很多个ngx_command_t,我大致列举了几个:
static ngx_command_t ngx_http_core_commands[] = {
...
{ ngx_string("server"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_MULTI|NGX_CONF_NOARGS,
ngx_http_core_server,
0,
0,
NULL },
//本配置项属于server相关的配置项,但它可以是main级别的
//也可以是srv级别的
{ ngx_string("connection_pool_size"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
//#define NGX_HTTP_SRV_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, srv_conf)
NGX_HTTP_SRV_CONF_OFFSET,
//connecton_pool_size相对于ngx_http_core_srv_conf_t的偏移
//ngx_http_core_pool_size_p解析完配置项的后续handler函数
offsetof(ngx_http_core_srv_conf_t, connection_pool_size),
&ngx_http_core_pool_size_p },
...
{ ngx_string("location"),
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
ngx_http_core_location,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("listen"),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
{ ngx_string("server_name"),
NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_server_name,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
...
#endif
ngx_null_command
};
server级别解析
可见,在解析http级别的配置项时,遇到server{}时,会调用ngx_http_core_module模块进行server级别的配置项的解析。与http级别配置项解析相同,它也会生成一个ngx_http_conf_ctx_t结构体。
具体流程如下:
1、创建ngx_http_conf_ctx_t结构体,main_conf将指向http块下的ngx_http_conf_ctx_t中的main_conf指针,而另两个数组重新分配空间。
2、通过调用create_srv_conf和create_loc_conf函数,按照模块的ctx_index加入到ngx_http_conf_ctx_t结构体的后两个数组中。
3、HTTP模块只有一个http级别的ngx_http_conf_ctx_t上下文,这是全局的。将由ngx_http_core_module模块生成的ngx_http_core_srv_conf_t结构体添加到全局
ngx_http_core_main_conf_t的servers数组中。便于以后将http级别与server级别的有关server配置项的合并。
4、继续调用ngx_conf_parse函数解析。
static char *
ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
char *rv;
void *mconf;
ngx_uint_t i;
ngx_conf_t pcf;
ngx_http_module_t *module;
struct sockaddr_in *sin;
ngx_http_conf_ctx_t *ctx, *http_ctx;
ngx_http_listen_opt_t lsopt;
ngx_http_core_srv_conf_t *cscf, **cscfp;
ngx_http_core_main_conf_t *cmcf;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
http_ctx = cf->ctx;
ctx->main_conf = http_ctx->main_conf;
/* the server{}'s srv_conf */
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/* the server{}'s loc_conf */
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
//只调用两个函数了
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf;
}
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
}
}
/* the server configuration context */
cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
cscf->ctx = ctx;
//ctx->main_conf指向http级别的ngx_http_conf_ctx_t的main_conf
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
//返回的是要插入的内存地址
cscfp = ngx_array_push(&cmcf->servers);
if (cscfp == NULL) {
return NGX_CONF_ERROR;
}
//插入cscf
*cscfp = cscf;
/* parse inside server{} */
pcf = *cf;
//重要 更新上下文!以便调用下面的ngx_conf_parse的时候上下文信息
//与当前级别相符。
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv == NGX_CONF_OK && !cscf->listen) {
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
sin = &lsopt.u.sockaddr_in;
sin->sin_family = AF_INET;
#if (NGX_WIN32)
sin->sin_port = htons(80);
#else
sin->sin_port = htons((getuid() == 0) ? 80 : 8000);
#endif
sin->sin_addr.s_addr = INADDR_ANY;
lsopt.socklen = sizeof(struct sockaddr_in);
lsopt.backlog = NGX_LISTEN_BACKLOG;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
lsopt.setfib = -1;
#endif
lsopt.wildcard = 1;
(void) ngx_sock_ntop(&lsopt.u.sockaddr, lsopt.addr,
NGX_SOCKADDR_STRLEN, 1);
if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
return rv;
}
location级别解析
在解析srv级别配置项时,如果发现location{}配置块则调用ngx_http_core_location方法。
(1) 在解析location{}配置块时,仍然会像解析Http块一样,先建立ngx_http_conf_ctx_t结构体,只是这里的main_conf和srv_conf都将指向所属的server块下的ngx_http_conf_ctx_t结构体的main_conf和srv_conf指针数组,而loc_conf则将指向重新分配的指针数组。
(2) 循环调用所有HTTP模块的create_loc_conf方法,将返回的结构体指针按照模块序号ctx_index保存到上述的loc_conf指针数组中。
(3) 如果在location中使用了正则表达式,那么这时将调用pcre_compile方法预编译正则表达式,提高性能。
(4) 第一个HTTP模块是ngx_http_core_module模块,它在create_loc_conf方法中将会生成ngx_http_core_loc_conf_t配置结构体,可以认为该结构体对应着当前解析的location块。这时会生成ngx_http_location_queue_t结构体,因为每一个ngx_http_core_loc_conf_t结构体都对应着一个ngx_http_location_queue_t,因此,此处将把ngx_http_location_queue_t串联成双向链表。
(5) 正式解析当前location{}配置块内的loc级别配置项。
配置项合并
合并就不说了,就是用到了结构体中特有servers、ctx以及locations这些成员将所有配置项关联起来。