在上一篇文章中,我们讲解了nginx http模块的存储结构,这个存储结构是我们理解http模块工作原理的基石。本文则主要讲解nginx是如何通过解析nginx.conf中的http配置块来一步一步构建http模块的这种存储结构的。
1. http配置块的解析
http配置块的解析方法定义在http
配置块对应的ngx_command_t
结构体中,如下是该结构体的定义:
static ngx_command_t ngx_http_commands[] = {
{ngx_string("http"),
NGX_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL},
ngx_null_command
};
通过该定义可以看出,http
配置块的解析工作主要是交由ngx_http_block()
方法进行。如下是该方法的源码:
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
char *rv;
ngx_uint_t mi, m, s;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t **cscfp;
ngx_http_core_main_conf_t *cmcf;
if (*(ngx_http_conf_ctx_t **) conf) {
return "is duplicate";
}
// 创建用于存储http模块配置数据的结构体数组
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;
// 计算NGX_HTTP_MODULE类型的模块的个数,并且为每个模块的ctx_index赋值
ngx_http_max_module = ngx_count_modules(cf->cycle, NGX_HTTP_MODULE);
// 申请存储配置main conf对象的内存空间
ctx->main_conf = ngx_pcalloc(cf->pool,sizeof(void *) * ngx_http_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
}
// 申请存储srv conf对象的内存空间
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
// 申请存储loc conf对象的内存空间
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
// 这里主要是分别调用各个http模块的create_main_conf()、create_srv_conf()和create_loc_conf()
// 方法,以生成对应模块的配置对象,然后将生成的对象放到ctx->main_conf、ctx->srv_conf和ctx->loc_conf
// 数组中。需要注意的是,在将生成的对象放到对应的数组中的时候,所存放的相对位置是通过各个模块的ctx->index
// 属性指定的,也就是其在当前类型的模块中的相对位置
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
// 对于http模块而言,这里的ctx字段指向的是一个ngx_http_module_t结构体对象
module = cf->cycle->modules[m]->ctx;
mi = cf->cycle->modules[m]->ctx_index;
if (module->create_main_conf) {
// 这里的main_conf对应的是解析http块中的NGX_HTTP_MAIN_CONF_OFFSET类型的指令
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_srv_conf) {
// 这里的srv_conf对应的是解析http块中的NGX_HTTP_SRV_CONF_OFFSET类型的指令
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_loc_conf) {
// 这里的loc_conf对应的是解析http块中的NGX_HTTP_SRV_CONF_OFFSET类型的指令
ctx->loc_conf[mi] = module->create_loc_conf(cf);
if (ctx->loc_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx; // 这里的ctx是ngx_http_conf_ctx_t类型的
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
// 这里主要是调用各个模块的preconfiguration()方法进行前置配置,
// 可以看到,这里的preconfiguration()方法主要是在解析http模块之前进行调用的
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
// 由于这里是对http块进行解析,因而filename不需要传值,只需要根据当前位置开始进行解析即可。
// 需要注意的是,调用完此方法之后,各个配置项的初始化方法也就解析完成了
// 另外需要说明的是,当前解析的是http模块,调用到这里之后,就会对http模块的子模块进行解析,也就是说,
// 当前方法调用完成之后,server、location等模块都已经解析完成
rv = ngx_conf_parse(cf, NULL);
if (rv != NGX_CONF_OK) {
goto failed;
}
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
// ngx_http_module_t
// mi记录了当前遍历的模块的配置的ctx_index,也即module参数创建的配置对应的位置
// 注意这里的module对象只是定义了当前模块进行相应的流程处理所需要执行的方法,其内部没有存储任何的与具体模块相关的数据对象
module = cf->cycle->modules[m]->ctx;
mi = cf->cycle->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;
}
}
// 这里主要是对各个模块的配置对象进行合并,因为有的配置项是既可以配置在http块,也可以配置在server块,
// 还可以配置在location块中的,这里会根据当前模块的要求,对相应的配置块进行合并
rv = ngx_http_merge_servers(cf, cmcf, module, mi);
if (rv != NGX_CONF_OK) {
goto failed;
}
}
// 省略部分代码...
// 调用各个模块的postconfiguration()方法进行配置解析完成之后的处理
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
// 省略部分代码
return NGX_OK;
}
这里展示的ngx_http_block()
方法的源码我们只展示了与http模块结构体的初始化相关的代码,省略的代码我们将在后面的文章中进行讲解。ngx_http_block()
方法可以说是解析http配置块的入口方法,该方法主要完成了如下:
- 创建用于存储整个http模块配置数据的结构体
ngx_http_conf_ctx_t
; - 计算当前所拥有的http模块的个数,并且会初始化各个模块中的
ctx_index
属性的值,将其标记为该模块在所有http模块中的相对位置,这个位置也就对应了当前模块在后面要讲解的main_conf
、srv_conf
和loc_conf
数组中的位置; - 分别为创建的
ngx_http_conf_ctx_t
结构体的main_conf
、srv_conf
和loc_conf
数组申请内存空间,而这些数组的长度就是前面计算得到的所有http模块的个数; - 遍历所有的模块,只要其为http模块,则依次调用其
create_main_conf()
、create_srv_conf()
和create_loc_conf()
方法,创建各个模块用于存储http配置块中的配置项结构体,需要注意的是,这些结构体的属性在此时还只是默认数据; - 遍历所有的http模块,调用其
preconfiguration()
方法进行配置文件解析之前的前置处理工作; - 调用
ngx_conf_parse()
方法,继续对http配置块进行解析,这里的解析工作其实就是对http配置块中的子配置项进行解析,不仅包括基础的配置项,也还包括server和location等配置块,在当前方法调用完成之后,整个http配置块都已经解析完成; - 遍历所有的http模块,调用其
init_main_conf()
方法,对各个模块的配置结构体属性设置默认值,由于上一步中已经解析完成了http配置块,因而配置文件中配置的属性是已经有值了的,这里的init_main_conf()
方法主要是对那些没有配置的属性设置默认值。这里在初始化了各个模块的配置项时,还会调用ngx_http_merge_servers()
进行配置项的合并,其主要作用就是在某个配置项在http块、server块或者location块中的两个及两个以上的配置块中都进行了配置时,将会选用哪一个值作为有效值的工作。这里的合并原理我们将在后面的文章进行讲解; - 遍历各个http模块,调用其
postconfiguration()
方法,进行配置解析完成之后的处理。
上面的方法,可以说对解析http配置块的整套流程都进行了处理,这里的入口主要是进行了一些数据结构的初始化,而最终解析各个配置项的工作则主要交由ngx_conf_parse()
方法进行。其中就包括server块和location块的解析。
2. server配置块的解析
对于server块的解析,其对应的ngx_command_t
结构体的配置如下:
{
ngx_string("server"),
NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS,
ngx_http_core_server,
0,
0,
NULL
}
可以看到,对于server块的解析工作,主要是交由ngx_http_core_server()
方法进行的。如下是该方法的源码:
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的类型为ngx_http_conf_ctx_t结构体,其存储了main_conf、srv_conf和loc_conf三个
// 属性的数组,这里需要特别注意,在ct->ctx也是一个ngx_http_conf_ctx_t类型的结构体,该属性在解析http
// 模块的时候已经进行了初始化,也就是说,这里的ctx->main_conf = http_ctx->main_conf;是将当前新创建的
// ctx的main_conf指向了其所在的http模块的main_conf,这样是可以达到一种复用的。
// 从这里可以看出,nginx在解析每个server块的时候,会每一个server块都新创建一个ngx_http_conf_t结构体,
// 用于存储当前正要解析的server块,而该结构体的main_conf会指向当前server块所在的http块的main_conf,
// 这样可以节省内存,而且这样做的另一个好处在于,后续进行server和http配置块的配置合并的时候,
// 只需要在每个server块对应的结构体中调用相应的合并方法即可
http_ctx = cf->ctx; // 存储http块的ngx_http_conf_t结构体
ctx->main_conf = http_ctx->main_conf;
// 为当前要解析的server块申请内存
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
// 为location申请内存
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; cf->cycle->modules[i]; i++) {
if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[i]->ctx;
// 调用各个模块的create_srv_conf()方法,以为每个模块创建其存储数据所需要的结构体对象
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[cf->cycle->modules[i]->ctx_index] = mconf;
}
// 调用各个模块的create_loc_conf()方法,以为每个模块创建其存储数据所需要的结构体对象
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
}
}
// 这里的ngx_http_core_module就是server配置项所在的模块定义,这里就是获取该模块的一个配置对象,
// 也即ngx_http_core_srv_conf_t类型对象。
// 这里的ngx_http_core_module.ctx_index的值为0
cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
cscf->ctx = ctx;
// 获取核心模块的配置对象,这里的main_conf上面只是将其指向了http块创建的main_conf数组,因而这里
// 获取到的也还是http模块中使用的同一个核心模块配置对象
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
// 将当前创建的
cscfp = ngx_array_push(&cmcf->servers);
if (cscfp == NULL) {
return NGX_CONF_ERROR;
}
*cscfp = cscf;
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;
// 这里开始解析其余的配置项,比如location配置块
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv == NGX_CONF_OK && !cscf->listen) {
// 这里主要是创建一个ngx_http_listen_opt_t结构体,并且将各个属性进行初始化
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
sin = &lsopt.sockaddr.sockaddr_in;
sin->sin_family = AF_INET;
#if (NGX_WIN32)
sin->sin_port = htons(80);
#else
// 如果当前用户对80端口没有权限,则使用8000端口
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
#if (NGX_HAVE_TCP_FASTOPEN)
lsopt.fastopen = -1;
#endif
lsopt.wildcard = 1;
// 这里主要是将当前的地址和端口的类型将其组织到lsopt.sockaddr.sockaddr中
(void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen,
lsopt.addr, NGX_SOCKADDR_STRLEN, 1);
if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
return rv;
}
对于server配置块的解析,其主要分为如下几个步骤:
- 创建用于存储SRV类型和LOC类型的模块的配置项的结构体数组,需要注意的是,这里并没有创建用于存储MAIN类型的模块的结构体数组,因为既然配置项是配置在server块中的,说明其一定不是MAIN类型的,因而这里只是将
main_conf
指向了http块的main_conf
数组; - 依次调用各个http模块的
create_srv_conf()
方法和create_loc_conf()
方法,创建这些模块用于存储SRV类型和LOC类型的配置结构体; - 找到解析http模块得到的配置数组中核心模块的配置结构体,将当前server块对应的
ngx_http_core_srv_conf_t
结构体添加到该核心模块的配置结构体的servers
数组中,通过这种方式将http配置块解析得到的结构体与各个server块解析得到的结构体进行关联; - 调用
ngx_conf_parse()
方法继续解析当前server块下的子配置项,包括各个子location,当前方法调用完成后,表示当前server块的解析已经完成; - 将当前server块配置的需要监听的端口添加到已有的端口列表中,以便于后续进行监听;
3. location配置块的解析
对于location配置块的解析,其处理过程与server块本质上是类似的,不过location配置块解析的过程中会处理location
配置项后面所指定的url匹配规则,并且会判断当前的匹配规则是否使用了正则表达式。另外,location配置块解析的时候需要注意,location配置块内部是可以有多个子location配置块的,这些子location配置块相当于是对父location配置块配置的一个补充,至于子location配置块的层级有多少个,nginx并没有做出限制。如下是对location
配置块进行定义的ngx_command_t
结构体:
{
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
}
可以看到,这里对location
配置块的解析工作主要交由ngx_http_core_location()
方法进行,如下是该方法的源码:
static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) {
char *rv;
u_char *mod;
size_t len;
ngx_str_t *value, *name;
ngx_uint_t i;
ngx_conf_t save;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx, *pctx;
ngx_http_core_loc_conf_t *clcf, *pclcf;
// 这里创建一个ctx对象,其主要是存储解析当前location配置块中的各个属性值
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
pctx = cf->ctx;
// 这里将当前ctx的main_conf和srv_conf分别指向当前location块所在的server和http块对应的结构体对象
ctx->main_conf = pctx->main_conf;
ctx->srv_conf = pctx->srv_conf;
// 为loc_conf申请空间,以存储解析当前location块得到的各个模块属性值
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; cf->cycle->modules[i]; i++) {
if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[i]->ctx;
// 如果当前模块的create_loc_conf()方法不为空,则调用该方法生成对应的配置
if (module->create_loc_conf) {
ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = module->create_loc_conf(cf);
if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
// 获取解析得到的核心模块的loc_conf值
clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
// 这里其实就是将核心模块的loc_conf指向当前location块对应的loc_conf值
clcf->loc_conf = ctx->loc_conf;
// 这里的elts是location配置块的参数个数,比如
// location / {
// root html;
// }
// 那么这里的参数个数就是2,分别为location和/
// 这里我们首先需要说明一下location的语法:
// - 语法:
// location [=|~|~*|^~] uri {...}
// location @name {...}
//- 使用规则:
// - 前缀字符串匹配:
// - 常规的字符串,比如`/html`,那么就匹配前缀为`/html`的URI;
// - `=`:精确匹配,那么URI就必须等于后面的字符串;
// - `^~`:如果匹配上了,就不再进行正则表达式匹配;
// - 正则表达式匹配:
// - `~`:大小写敏感的正则匹配;
// - `~*`:忽略大小写的正则匹配;
// - 用于内部跳转的命名location:
// - `@`
// - 合并连续的`/`符号:
// - `merge_slashes on;`
value = cf->args->elts;
// 判断当前的参数个数是否为3
if (cf->args->nelts == 3) {
// 这里下标为1的参数即为第二个参数
len = value[1].len;
mod = value[1].data;
name = &value[2];
if (len == 1 && mod[0] == '=') {
// 这里的exact_match字段标志了当前匹配方式是否为完全匹配,
// 由于当前是=操作,因而将其标志为1
clcf->name = *name;
clcf->exact_match = 1;
// 如果当前是^~,则不进行正则匹配
} else if (len == 2 && mod[0] == '^' && mod[1] == '~') {
clcf->name = *name;
clcf->noregex = 1;
// 如果当前是~,则表示当前是大小写敏感的正则匹配
} else if (len == 1 && mod[0] == '~') {
if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
// 如果当前是~*,则表示其为大小写不敏感的正则匹配
} else if (len == 2 && mod[0] == '~' && mod[1] == '*') {
if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid location modifier \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
} else {
// 这里else分支的功能与上面if分支的功能基本上是一致的,主要区别在于下面允许第二个和第三个元素之间是
// 没有空格的,除此之外,下面的else分支还处理了一种情况就是location后的参数是以@符号开头的,
// 这种情况是会转发当前的请求到其他的location中的
name = &value[1];
if (name->data[0] == '=') {
clcf->name.len = name->len - 1;
clcf->name.data = name->data + 1;
clcf->exact_match = 1;
} else if (name->data[0] == '^' && name->data[1] == '~') {
clcf->name.len = name->len - 2;
clcf->name.data = name->data + 2;
clcf->noregex = 1;
} else if (name->data[0] == '~') {
name->len--;
name->data++;
if (name->data[0] == '*') {
name->len--;
name->data++;
if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
return NGX_CONF_ERROR;
}
} else {
if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
} else {
clcf->name = *name;
// 处理以@符号开头的情况
if (name->data[0] == '@') {
clcf->named = 1;
}
}
}
// 如果当前location是嵌入某个location中的,那么pclcf指向的就是父location的解析值,
// 如果没有父location,那么这里的cf->cmd_type就不等于NGX_HTTP_LOC_CONF
pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
if (cf->cmd_type == NGX_HTTP_LOC_CONF) {
/* nested location */
#if 0
clcf->prev_location = pclcf;
#endif
// 如果父location是精确匹配,那么其是不能有子location的
if (pclcf->exact_match) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"location \"%V\" cannot be inside "
"the exact location \"%V\"",
&clcf->name, &pclcf->name);
return NGX_CONF_ERROR;
}
// 如果父location使用了@进行重定向,那么其是不能有子location的
if (pclcf->named) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"location \"%V\" cannot be inside "
"the named location \"%V\"",
&clcf->name, &pclcf->name);
return NGX_CONF_ERROR;
}
// 使用@进行重定向的location只允许出现在server块下,不能出现在嵌套的子location中
if (clcf->named) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"named location \"%V\" can be "
"on the server level only",
&clcf->name);
return NGX_CONF_ERROR;
}
len = pclcf->name.len;
#if (NGX_PCRE)
// 这里的len是父location的表达式的长度,因而这里就是比较子location是否是以父location为前缀开头的,
// 如果不是,则返回异常
if (clcf->regex == NULL && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
#else
if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
#endif
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"location \"%V\" is outside location \"%V\"",
&clcf->name, &pclcf->name);
return NGX_CONF_ERROR;
}
}
if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
save = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_LOC_CONF;
// 继续解析配置文件的子模块配置
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
这里对location
配置块的解析工作主要分为如下几个步骤进行:
- 申请用于存储
location
配置块中各个模块的配置项的配置的结构体数组,并将其保存到loc_conf
属性中,而其main_conf
和srv_conf
则直接继承自当前location块所在的server块对应的结构体的main_conf
和srv_conf
; - 依次调用各个http模块的
create_loc_conf()
方法,创建用于存储各个模块的配置项的结构体; - 解析
location
配置项后面的用于匹配url的表达式; - 进行父子location的校验工作,比如,父location是精确匹配的,那么此时是不能有子location的。这项校验工作主要用于有嵌套location的情况;
- 继续调用
ngx_conf_parse()
方法解析当前location配置块下的子配置项,也包括子location的解析。可以看出来,这里对子location的解析过程实际上就是一个递归解析的过程,而解析所使用的方法还是上面的ngx_http_core_location()
方法。
4. 小结
本文主要从源码的角度对nginx是如何解析http块、server块和location块的过程进行了详细讲解,着重说明了其是如何构建nginx http模块的配置结构体的。
来源:oschina
链接:https://my.oschina.net/zhangxufeng/blog/3167697