-------------------
先看下下面这个数据结构:
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf;
} ngx_http_conf_ctx_t;
这里面有三个指针数组;数组的长度相同,都是 Nginx 加载的模块的总数;三个数组相同位置上的元素的类型相同,如果 main_conf[3] 是 ngx_http_gzip_conf_t 类型的数据的话,srv_conf 和 loc_conf 也是。
以 Gzip Filter Module 为例:
static ngx_http_module_t ngx_http_gzip_filter_module_ctx = {
ngx_http_gzip_add_variables, /* preconfiguration */
ngx_http_gzip_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_gzip_create_conf, /* create location configuration */
ngx_http_gzip_merge_conf /* merge location configuration */
};
loc_conf[n] 里面的内容,就是 ngx_http_gzip_create_conf 的返回值,也就是创建并经过初始化的ngx_http_gzip_conf_t 类型的结构体数据。
没有定义创建 main_conf 和 srv_conf 的函数,所以 main_conf[n] 和 srv_conf[n] 为 NULL。
配置分三个级别:http,server,location,每一级别的每一个对象都会有一个 ngx_http_conf_ctx_t 结构来存储各个模块在各个级别的配置信息。
http block 中 ctx 的初始化
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
// ...
/* 初始化 http block 的 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;
/* count the number of the http modules and set up their indices */
// 原英文注释够清楚了
ngx_http_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
ngx_modules[m]->ctx_index = ngx_http_max_module++;
}
/*
* the http null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/
ctx->main_conf = ngx_pcalloc(cf->pool,
sizeof(void *) * ngx_http_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the http null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) *ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the http null loc_conf context, it is used to merge
* the server{}s' loc_conf's
*/
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) *ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* create the main_conf's, the null srv_conf's, and the null loc_conf's
* of the all http modules
*/
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;
}
}
}
// ...
}
代码很简单,就不解释了
server block 配置的初始化是在 ngx_http_core_server 里面,和 http block 类似;
server block 只初始化 srv_conf 和 loc_conf,无 main_conf,它的 main_conf 指向 http 级别的main_conf。
location block 配置的初始化是在 ngx_http_core_location 里面,main_conf 和 srv_conf 都指向 server 级别的配置,只负责初始化 loc_conf。
配置文件解析过程
---------------------------
ngx_init_cycle 函数里面,先调用 ngx_conf_param 处理命令行的配置选项(惯例,命令行和配置文件都支持),然后执行 ngx_conf_parse(&conf, &cycle->conf_file) 进行配置文件的解析。
基本处理流程:
1) 先设置要解析的配置文件的类型 module_type 和 cmd_type,ngx_init_cycle 里面的初始化是这样的:
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
那问题来了,那些是 NGX_CORE_MODULE 呢?http、events、mail、error_log、ngx_core_module 等都是 NGX_CORE_MODULE。
而 http、mail、event 等 module 还包含了多个子 module。
http 的子 module 的类型是 NGX_HTTP_MODULE;event 的子 module 是 NGX_EVENT_MODULE。
解析是先从 NGX_CORE_MODULE 类型的 module 开始的。
2) 解析过程,ngx_init_cycle 里面从解析 NGX_CORE_MODULE 类型的配置开始,一行一行的解析文件。
对于每一行,执行以空格(或者 \t 等) 为分隔符字符串 split,split 的结果存储在一个数组里面;
数组的第一个元素就是配置指令;遍历所有这种类型的 module,查找该指令出现在那个模块,然后执行该模块为该指令定义的回调函数来设置相关参数。
对于含有子 module 的模块,通常会在指令的回调函数里面,重新设置 module_type 和 cmd_type ,再次执行 ngx_conf_parse 来解析所有的子模块的配置,止到无子模块为止。
相关源码解析
------------
ngx_conf_parse 函数
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
//...
if (filename) {
// 第一次调用时,会传入配置文件路径,这里打开配置文件,设置相应 buffer,和 文件的 offset 等
// 以后调用,该参数保持为空即可
//...
} else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
// 基本上都是 parse block
type = parse_block;
} else {
// 这个用来解析命令行参数
type = parse_param;
}
for ( ;; ) {
// 读取一行,然后以空格或者 tab 进行 split,各个元素以数组形式存储在 cf->args 成员里面
rc = ngx_conf_read_token(cf);
/*
* ngx_conf_read_token() may return
*
* NGX_ERROR there is error
* NGX_OK the token terminated by ";" was found
* NGX_CONF_BLOCK_START the token terminated by "{" was found
* NGX_CONF_BLOCK_DONE the "}" was found
* NGX_CONF_FILE_DONE the configuration file is done
*/
//...
// 寻找该行配置指令的回调函数,然后执行设置相关参数,下面详细分析
rc = ngx_conf_handler(cf, rc);
/..
}
//...
}
ngx_conf_handler 函数
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
//...
name = cf->args->elts;
// 开始遍历所有 module
for (i = 0; ngx_modules[i]; i++) {
/* look up the directive in the appropriate modules */
if (ngx_modules[i]->type != NGX_CONF_MODULE
&& ngx_modules[i]->type != cf->module_type)
{
continue;
}
// 获取该 module 定义的所有 commands
cmd = ngx_modules[i]->commands;
if (cmd == NULL) {
continue;
}
// 搜索所有 commands
for ( /* void */ ; cmd->name.len; cmd++) {
if (name->len != cmd->name.len) {
continue;
}
if (ngx_strcmp(name->data, cmd->name.data) != 0) {
continue;
}
// 查找成功,下面对该指令的类型、参数做检查
/* is the directive's location right ? */
if (!(cmd->type & cf->cmd_type)) {
if (cmd->type & NGX_CONF_MULTI) {
multi = 1;
continue;
}
goto not_allowed;
}
if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" is not terminated by\";\"",
name->data);
return NGX_ERROR;
}
//...
/* set up the directive's configuration context */
conf = NULL;
// 获取相关的 ctx,这是一个配置的指针数组,前面有讲
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[ngx_modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}
}
// 执行回调函数
rv = cmd->set(cf, cmd, conf);
// ...
}
}
对于 http block,它执行的回调函数是 ngx_http_block,里面会继续对所有子 http module 进行解析,相关代码如下:
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//...
/* parse inside the http{} block */
// 设置要解析的 module 类型为 NGX_HTTP_MODULE
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
// 开始解析子 module
rv = ngx_conf_parse(cf, NULL);
/...
}