您的位置:首页 > 运维架构 > Nginx

nginx源代码分析--配置文件解析

2014-07-11 18:16 211 查看

ngx-conf-parsing

对 Nginx 配置文件的一些认识:

配置指令具有作用域,分为全局作用域和使用
{}
创建其他作用域。
同一作用域的不同的配置指令没有先后顺序;同一作用域能否使用同样的指令,和对相 同指令的处理由各模块自行决定
整个 Nginx 的执行时各模块行为都和配置指令密切相关
每一个配置指令都仅仅能在预先定义好的作用域中使用
配置指令解析使用递归的方式

配置解析相关代码量巨大,本文势必非常冗长。所以,都要做好心理准备。同一时候,因为
mail
模块平时使用实在不多,故,本文仅仅对
http
应用场景下的配置解析过程进行分析。

同一时候,为了行文方便,先约定一些名称:

配置文件 – 指 Nginx 的主配置文件
nginx.conf
。在配置文件里使用
include
引入其他配置文件的方式这里不做分析。

配置指令 – 配置文件里的基本单位,配置指令可接收0个或多个配置指令參数

配置项 – 配置指令相应的内部存储。一个配置指令的值可能会影响到多个配置项的 值。

配置项结构体 – 每一个模块相关的配置项集中管理用的结构体。

作用域模块配置数组 – 每一个作用域相应的一个
void *
类型的数组。每一个
void *
指针指向可用于此作用域的模块相应的配置项结构体

基本概念

Nginx 的模块使用
ngx_module_t
结构表示

Nginx 按分类和等级,将模块划分为
NGX_CORE_MODULE
NGX_EVENT_MODULE
NGX_HTTP_MODULE

NGX_MAIL_MODULE
等类型,由
ngx_module_t.type
存储。

Nginx 的配置指令使用
ngx_command_t
结构进行 “声明”。模块支持的配置指令由
ngx_module_t.commands
数组存储。

模块上下文 (
module context
) 定义了针对模块配置的一系列操作 (申请配置存储、 继承上级模块的配置等等),不同的操作在配置分析的不同阶段被调用。模块上下文依据模 块类型分为
ngx_core_module_t
ngx_event_module_t
ngx_http_module_t
ngx_mail_module_t
等等。模块上下文由
ngx_module_t.ctx
存储。

配置指令的作用域。不论什么一个配置指令都有其可以使用的范围,即其作用域。配置指令如 果在其作用域外使用,Nginx 都会打印错误信息并退出。配置指令作用域眼下有
NGX_DIRECT_CONF
、NGX_MAIN_CONF[/code]、
NGX_EVENT_CONF
NGX_HTTP_MAIN_CONF
NGX_HTTP_SRV_CONF
NGX_HTTP_LOC_CONF
NGX_MAIL_MAIN_CONF
NGX_MAIL_SRV_CONF

等。

一个配置指令能够在多个作用域中定义。
在父作用域里定义的指令,也会被子作用域继承。可是,假设子作用域也使用了同一 个指令,子作用域的指令值会覆盖高级别作用域的指令。

配置指令的參数。配置指令能够接受 0 个或多个參数。它能接受的參数个数也须要在定 义配置指令时明白声明,以便
Nginx
在解析配置过程中,对配置文件正确性进行检查。

有一些配置指令,比方,
http
,
event
,
server
,
location
,
mail
,
types
等,用来显式的定义作用域,它们被标识为
NGX_CONF_BLOCK
,表示一个
{}
块的開始。

配置指令的作用域和可接受參数等信息,由
ngx_command_t.type
字段存储。

配置指令解析函数。基本每一个配置指令 (简单的单值指令除外) 都定义了对应的回调函数。
Nginx
在解析配置过程中,假设碰到了该指令,会调用此回调函数对指令进行进一步的解 析。

下图是一个简单
nginx.conf
的作用域示意图:





Nginx 配置存储的内部结构,也是按作用域组织的。每一个作用域都为其支持指令能够出现 在该作用域的模块预留有位置 (一个
void *
)。

默认配置情况下,Nginx 启用的模块相应的类型 (模块按启用顺序从上到下) 例如以下:

------------------------------------------------------
module                          type
------------------------------------------------------
ngx_core_module                 NGX_CORE_MODULE
ngx_errlog_module               NGX_CORE_MODULE
ngx_conf_module                     NGX_CONF_MODULE
ngx_events_module               NGX_CORE_MODULE
ngx_event_core_module               NGX_EVENT_MODULE
ngx_epoll_module                    NGX_EVENT_MODULE
ngx_http_module                 NGX_CORE_MODULE
ngx_http_core_module                NGX_HTTP_MODULE
ngx_http_log_module                 NGX_HTTP_MODULE
ngx_http_upstream_module            NGX_HTTP_MODULE
ngx_http_autoindex_module           NGX_HTTP_MODULE
...                                 NGX_HTTP_MODULE


准备工作

配置文件解析有两种场景:

Nginx 进程启动时,读取配置文件,并依据配置完毕对应初始化。
Nginx 收到又一次载入配置文件的信号后,读取配置文件,并依据当前配置文件完毕相成相 应初始化。随后,释放依据老配置文件申请的资源 (被新配置反复利用的除外)。

本文也仅仅针对第一种场景进行分析。

在配置解析開始之前,Nginx 要为配置解析过程分配一个持久内存池和暂时内存池。从持久 内存池中申请的内存,用于存储从配置文件里解析完毕并正确初始化的配置结构体;而暂时 内存池中申请的内存,仅仅在解析过程中作为暂时存储,待配置解析完毕后,就会被回收。

随后,Nginx 准备第一层级的模块配置索引数组,第一层级仅仅存储
NGX_CORE_MODULE
模 块的配置结构体,这一层级的作用域为
NGX_MAIN_CONF


------------core/ngx_cycle.c:183------------------------
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
...
if (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = ngx_modules[i]->ctx;
if (module->create_conf) {
rv = module->create_conf(cycle);
...
cycle->conf_ctx[ngx_modules[i]->index] = rv;
}
}

解析框架

配置解析流程相当简洁标准:准备当前作用域上下文,读取指令并分析指令參数,最后再调 用指令相应的回调函数。

拿第一层级
NGX_MAIN_CONF
作用域来说,其解析过程大致例如以下:

------------core/ngx_cycle.c:246------------
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
...
ngx_conf_parse(&conf, &cycle->conf_file);

for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = ngx_modules[i]->ctx;

if (module->init_conf) {
module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index]);
...
}
}

上面的代码解析
NGX_CORE_MODULE
类型的模块,解析出来的指令位于作用域
NGX_MAIN_CONF
中。同一时候,解析完毕的数据,按模块存放于
conf.ctx
中。在配置文件 解析完毕后,Nginx 调
NGX_CORE_MODULE
类模块的
init_conf
函数依据已经读取到的 配置完毕进一步初始化操作。

ngx_conf_parse
是配置解析的入口函数,它依据当成上下文读取配置文件数据,进行分 析的同一时候对配置文件语法进行检查。同一时候,这个函数会在指令处理函数改动作用域等上下文 信息后,被间接递归调用。

以下就来看一看
ngx_conf_parse
的运行流程。

----------core/ngx_conf_file.c:101----------------
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
ngx_buf_t buf;

if (filename) {
fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
...
cf->conf_file = &conf_file;
...
cf->conf_file->buffer = &buf;
...
buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
...
cf->conf_file->file.fd = fd;
...

type = parse_file;

} else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {

type = parse_block;

} else {
type = parse_param;
...

for ( ;; ) {
rc = ngx_conf_read_token(cf);
...
rc = ngx_conf_handler(cf, rc);
}
...
}

第一次调用
ngx_conf_parse
时,函数会打开配置文件,设置正在运行的解析类型,读取
token
,然后调用
ngx_conf_handler
。那么
ngx_conf_handler
又做了些什么呢?

-------------core/ngx_conf_file.c:279-------------
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
...
for (i = 0; ngx_modules[i]; i++) {
/* module type checking */
...
cmd = ngx_modules[i]->commands;
...
for ( /* void */ ; cmd->name.len; cmd++) {
/* name comparison */
...
/* namespace checking */
...
/* checking argument numbers */
...
/* set up the directive's configuration context */

conf = NULL;

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];
}
}

rc = cmd->set(cf, cmd, conf);
...
}
}
}

对上述代码的几点补充:

全然读取到一条配置指令后,Nginx 会使用该指令名在全部指令定义中进行查找。可是, 在进行查找之前,Nginx 会先验证模块的类型和当前解析函数上下文中的类型是否一致。 随后,在某个模块中找到匹配的指令定义后,还会验证指令能够出现的作用域是否包括当前 解析函数上下文中记录的作用域。最后,检查指令的參数个数是否和指令定义中标明的一 致。

校验工作完毕后,Nginx 将指令名和全部模块提前定义支持的指令进行对照,找到全然匹配 的配置指令定义。依据配置指令的不同类型,配置项的存储位置也不同。

NGX_DIRECT_CONF
类型的配置指令,其配置项存储空间是全局作用域相应的存储空 间。这个类型的指令主要出如今
ngx_core_module
模块里。

conf = ((void **) cf->ctx)[ngx_modules[i]->index];

NGX_MAIN_CONF
表示配置指令的作用域为全局作用域。纵观 Nginx 整个代码, 除了
ngx_core_module
的配置指令 (同一时候标识为
NGX_DIRECT_CONF
) 位于这个 作用域中外,另外几个定义新的子级作用域的指令 –
events
http
mail
imap
。 非
NGX_DIRECT_CONF
NGX_MAIN_CONF
指令在全局作用域中并未被分 配空间,所以在指令处理函数中分配的空间须要挂接到全局作用域中。

conf = &(((void **) cf->ctx)[ngx_modules[i]->index];

其他类型配置指令项的存储位置和指令出现的作用域 (而且非全局作用域) 有关:

confp = *(void **) ((char *) cf->ctx + cmd->conf);

if (confp) { conf = confp[ngx_modules[i]->ctx_index]; }

配置项将要存储的位置确定后,调用指令回调函数,完毕配置项初始化和其他工作。

rc = cmd->set(cf, cmd, conf);


配置文件解析框架大抵如此,再进行更具体的代码分析之前,先来看一张配置文件解析后的 配置结构图:





指令读取

了解了大致框架,如今再从细节着手。接下来分析一下配置指令读取和分解函数,也就是
ngx_conf_read_token
函数的实现。

ngx_conf_read_token
将从磁盘配置文件读取部分数据到内存
buffer
,然后再依照 Nginx 支持的格式从
buffer
中读出一条合法的指令。假设,
buffer
中数据不足以构 成一条完整的指令,则须要再从配置文件里读取很多其它数据。

配置文件读取

buffer
中数据不足时,也就是
b->pos >= b->last
时,从配置文件里读取很多其它数据。

--------------core/ngx_conf_file.c:463---------------
if (b->pos >= b->last) {
...
len = b->pos - start;

if (len) {
ngx_memcpy(b->start, start, len);
}

size = (ssize_t) (file_size - cf->conf_file->file.offset);

if (size > b->end - (b->start + len)) {
size = b->end - (b->start + len);
}

n = ngx_read_file(&cf->conf_file->file, b->start + len, size,
cf->conf_file->file.offset);
b->pos = b->start + len;
b->last = b->pos + n;
start = b->start;
}

对上述代码的补充说明:

ngx_buf_t
结构简单描这一下其字段 ———–core/ngx_buf.h:19—————– struct ngx_buf_s { u_charpos; / b 中还未使用的数据起始位置
/ u_char *last; / b 中存储的有效数据结束位置 / … u_char *start; / 连续内存块 b 的起始位置/ u_char *end; / 连续内存块 b 的结束位置 */ … }; typedef struct ngx_buf_s ngx_buf_t;

buffer
中有效数据已经用尽时 (
b->pos >= b->last
),一条指令的某个组成部分 (从
start
開始到
b->pos
结束
len
字节) 可能还未被完整读取,这部分数据须要 保留。

ngx_memcpy(b->start, start, len);


buffer
如今就腾出了
b->end - (b->start + len)
的可用空间。

start
始终指向一条指令某个组成部分的起始位置。

语法状态机

ngx_conf_read_token
剩余部分实现了逐字符分析指令行语法的状态机。这部分代码位于
core/ngx_buf.h:593-734
。语义的正确性验证在配置解析的基它部分完毕。

指令行允行出现的格式 (使用正则定义,对语法定义不熟,凑乎看啊 /* TODO */):

line := token token? { line+ }
| token space token? space?;
token := ["'][ \t\r\na-z0-9\\$]+["']
| [a-z0-9$]+
| ( token )
space := [ \t\r\n]+


last_space
用于表示上一个字符是否为
space
。初始值为
1


need_space
用于表示状态机要求下一个字符必须为
space
(或者 ‘;’, ‘{‘, ‘)’)。 初始值
0


start
指向正在分析中的
token
起始位置。初始值为
b->pos


d_quoted
用于表示当前字符处于
""
中。初始值为
0


s_quoted
用于表示当前字符处于
''
中。初始值为
0


sharp_comment
用于表示当前字符是凝视的一部分。初始值为
0


variable
用于表示当前字符是变量名的一部分。初始值为
0


quote
用于表示当前字符前一个字符是否为
\
。初始值为
0

cf->args
存储指令名和全部參数。它是
ngx_str_t
类型的数组。

这部分的代码实在太长 (200多行) 这里仅仅将框架缩略例如以下

for ( ;; ) {
ch = *b->pos++; /* 当前字符存储于 ch 中 */

if (ch == LF) {
if (sharp_comment) {  /* # 是行凝视 */
sharp_comment = 0;
}
}

if (sharp_comment) {    /* 忽略 # 后的全部字符,直到进行新行为止 */
continue;
}

if (quoted) {           /* 不再单独处理 \ 后的字符,后面读取 token
quoted = 0;            时,会做专门处理 */
continue;
}

if (need_space) {       /* 此字符必须为 space 分隔符 */
/*
忽略掉 space 字符;
假设字符是 ';','{',此次指令行读取完毕;
假设字符是 ')',開始识别新的 token;
假设读到其他字符则解析失败
*/
}

if (last_space) { /* 找到新 token 的開始位置 - 第一个非 space 字符 */
/*
忽略掉 space 字符;
假设字符是 ';''{''}',此次指令行读取完毕;
假设字符是 '#',该字符后面都是凝视;
假设字符是 '\',下一个字符将被忽略;
假设字符是 '"', ''', 下一个字符是被引用的内容;
*/
} else { /* 開始读取新 token */
/*
假设字符是 '$',后面的非 space 字符组成变量 token;
碰到了结束引號 '"', ''' 时,token 读取完毕;
碰到了 space 字符,token 读取完毕;
*/
if (found) {
/*
将找到的 token 追加到 cf->args 数组中,而且每一个 token 字符
串以 '\0' 结束
*/
}
}
}

作用域解析

events
作用域

events
作用域由
ngx_events_module
events
指令处理创建。其定义为:

-------------event/ngx_event.c:83------------
{ ngx_string("events),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_events_block,
0,
0,
NULL }

開始配置文件解析之前,
ngx_events_module
在全局作用域中配置结构体指针值为 NULL, 所以,
ngx_events_block
函数的第一件事情就是,创建配置结构体,并挂载到全局作用 域上。

------------event/ngx_event.c:902------------
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
...
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
...
*(void **) conf = ctx;

对上述代码的补充说明:

为什么要多次一举的多一次中转,使用
ctx
指向
void *
类型指针,而此指针再指 向作用域模块配置数组呢? 接着往下看!

然后,调用类型为
NGX_EVENT_MODULE
的模块上下文中的
create_conf
函数,为各个 模块在此作用域分配配置解析体。拿
ngx_epoll_module
举例,其
create_conf
函数 从内存池中申请用于存储
ngx_epoll_conf_t
的内存块,并挂接到
events
作用域中的 对应位置。

随后,如上文已经描写叙述过的,切换配置解析上下文,然后调用
ngx_conf_parse
函数解析 此作用域相应的配置文件
block
中出现的指令行:

-----------event/ngx_event.c:929-------------
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;

rv = ngx_conf_parse(cf, NULL);

*cf = pcf;

epoll
模块举例,来看下在上面的上下文环境中,碰到它支持的指令
epoll_events
时,整个过程是怎样进行的。

----------event/modules/ngx_epoll_module.c:129------------
{ ngx_string("epoll_events"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,                                         /* conf */
offsetof(ngx_epoll_conf_t, events),        /* offset */
NULL },

ngx_conf_read_token
成功返回后,
ngx_conf_handler
依据该指令类型和上下文定 位到指令相应的配置项所在的位置:

----------core/ngx_conf_file.c:386------------------------
confp = *(void **) ((char *) cf->ctx + cmd->conf)

if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}

对上述代码的补充说明:

cf->ctx
指向在
ngx_events_block
中分配的
void *
指针,
void *
指针进而 又指向
events
作用域模块配置数组。所以,
confp
最后指向了
events
作用域模块 配置数组,
conf
就指向了
ngx_epoll_module
相应的配置项结构体,即
ngx_epoll_conf_t


epoll_events
指令的处理函数是通用的处理函数
ngx_conf_set_num_slot


------------core/ngx_conf_file.c:1182-------------------
char *
ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *p = conf;
...
np = (ngx_int_t *) (p + cmd->offset);
...

*np = ngx_atoi(value[1].data, value[1].len);
}

对上述代码的补充说明:

cmd->offset
值是
offsetof(ngx_epoll_conf_t, events)
,即配置指令
epoll_events
对就的配置项
ngx_uint_t events
在配置结构体
ngx_epoll_conf_t
中的偏移。Nginx 提前定义处理函数基本都使用这种方式,以实现通用性。

终于,
epoll_events
指令的參数,经过上面的一番周折,被存储到了
events
作用域 里
ngx_epoll_module
的配置结构体
ngx_epoll_conf_t
events
成员中。

events
作用域下的其他指令的解析过程和
epoll_events
都大同小异,就略过不表了。 以下搞一下
http
作用域。

http
作用域

http
作用域由
ngx_http_module
http
指令处理函数创建。其定义为:

-------------event/ngx_event.c:83------------
{ ngx_string("http),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL }

events
作用域类似,開始配置文件解析之前,
ngx_http_module
在全局作用域中配 置结构体指针值为 NULL,所以,
ngx_http_block
函数的第一件事情也是,创建配置结构 体,并挂载到全局作用域上。

------------http/ngx_http.c:128--------------
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
...
*(ngx_http_conf_ctx_t **) conf = ctx;

可是,
http
作用域和
events
作用域的地方是,
ctx
并非指向一个
void *
,而 是一个
ngx_http_conf_ctx_t
结构体。

------------http/ngx_http_config.h:16--------
typedef struct {
void        **main_conf;
void        **srv_conf;
void        **loc_conf;
} ngx_http_conf_ctx_t;

造成这点差别的原因是:

http
作用域是能够嵌套的。一个
http
作用域中以嵌套多个
server
作用域,一个
server
作用域又能够嵌套多个
location
作用域。

同一时候,
NGX_HTTP_MODULE
类型的模块的指令能够定义在
http
作用域中 (
NGX_HTTP_MAIN_CONF
),也能够定义在
server
作用域 (
NGX_HTTP_SRV_CONF
), 也能够定义在
location
作用域 (
NGX_HTTP_LOC_CONF
)。其实,大部分的 HTTP 模块 支持的指定都同一时候定义在这三个作用域中。

NGX_HTTP_MODULE
类型的模块将自己支持的指令相应的配置项分为三类:第一类的指令 仅仅能在
NGX_HTTP_MAIN_CONF
作用域中使用;第二类的指令能够在
NGX_HTTP_MAIN_CONF

NGX_HTTP_SRV_CONF
作用域中使用;第三类能够在
NGX_HTTP_MAIN_CONF
NGX_HTTP_SRV_CONF
NGX_HTTP_LOC_CONF
作用域中使用。这三类指令相应的配置项 分为三个结构体存储
ngx_http_xxx_main_conf_t
ngx_http_xxx_srv_conf_t

ngx_http_xxx_loc_conf_t


main_conf
,
srv_conf
loc_conf
就是给三类配置结构体提供的作用域模块配置 数组。在
http
作用域,三个数组都是须要的;在
server
作用域和 父作用域
http
使用同一个
main_conf
;在
location
作用域,和其父
server
作用域使用同一个
main_conf
srv_conf
。 `

ngx_http_block
中分配完
http
作用域的三类作用域模块配置数组后,会调用 各个
NGX_HTTP_MODULE
类型的模块上下文定义的
create_main_conf
,
create_srv_conf
create_loc_conf
回调函数分别在三类作用域模块配置数组创建 模块的三类配置项结构体:

-------------http/ngx_http.c:150---------------
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 (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
...
}

if (module->create_loc_conf) {
ctx->loc_conf[mi] = module->create_loc_conf(cf);
...
}
}

接下来,和
events
作用域一样,切换配置解析上下文,调用
ngx_conf_parse
函数:

-------------http/ngx_http.c:214------------
pcf = *cf;
cf->ctx = ctx;
...
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
...
*cf = pcf;

http
作用域中的普通指令解析和上面介绍的
events
作用域中
ngx_epoll_module
epoll_events
解析过程大同小异。

接下来主要分析一下
http
作用域中
server
作用域的创建及
server
中子作用域
location
的创建以及多个
location
作用域是怎样挂接到其父作用域
server
上的 和多个
server
作用域是怎样挂接到它的父作用域
http
上的。

server
作用域

配置解析函数如今处于
http
作用域的上下文中: *
cf->ctx
指向
ngx_http_conf_ctx_t
*
cf->module_type
值为
NGX_HTTP_MODULE
*
cf->cmd_type
值为
NGX_HTTP_MAIN_CONF


解析函数碰到
server {}
后,会调用
server
指令的处理函数
ngx_http_core_server


    -------------http/ngx_http_core_module.c:2300------------
static char *
ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
...http_ctx = cf->ctx;
ctx->main_conf = http_ctx->main_conf;

ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_moudle);
...
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
...
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);
...
*cscfp = cscf;

pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;

rv = ngx_conf_parse(cf, NULL);

*cf = pcf;
}

对上述代码的补充说明:

server
作用域和其父作用域共用一个
main_conf


server
作用域也要使用
ngx_http_conf_ctx_t
代表此作用域的三类作用域模块配置 数组

server
作用域的
ngx_http_conf_ctx_t
存储到
ngx_http_core_module
配置项 结构体
ngx_http_core_srv_conf_t

ctx


ngx_http_core_module
负责在
http
及其各个子作用域中起关联作用。刚刚解析的
server
作用域,会被存储 (通过
ngx_http_core_srv_conf_t
间接存储) 到
ngx_http_core_main_conf_t
servers
数组中。

配置函数再次调用之前,切换上下文:

pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;

rv = ngx_conf_parse(cf, NULL);

*cf= pcf;

以下,拿
listen
指令作用样例,分析一下当前作用域的指令解析流程。

listen
指令的定义为:

    --------------http/ngx_http_core_module.c:278---------
{ ngx_string("listen"),NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
ngx_http_core_listen,NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },

ngx_conf_read_token
从配置文件里读取完此指令后,由
ngx_conf_handler
校验指令 的合法性,然后找到指令项所在结构体的存储位置,即

confp = *(void **) ((char *) cf->ctx + cmd->conf);

if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}

对上述代码的补充说明:

cf->ctx
如今指向进入
server
作用域后,申请的
ngx_http_conf_ctx_t
cmd->conf
值是
NGX_HTTP_SRV_CONF_OFFSET
,即
offsetof(ngx_http_conf_ctx_t, srv_conf)
。 那么
confp
终于指向了新申请的
ngx_http_conf_ctx_t
srv_conf
指向的作用域 模块配置数组

conf
指向了
server
作用域的
ngx_http_core_module
ngx_http_core_srv_conf_t


随后,调用
listen
指令处理函数
ngx_http_core_listen


    -------------http/ngx_http_core_module.c:3256--------
static char *
ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_srv_conf_t *cscf = conf;
...
cscf->listen = 1;
...
ngx_http_add_listen(cf, cscf, &lsopt);
...
}

------------http/ngx_http.c:1105-----------------
ngx_int_t
ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_listen_opt_t *lsopt)
{
...
ngx_http_conf_port_t *port;
ngx_http_core_main_conf_t *cmcf;
...
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
...
port = ngx_array_push(cmcf->ports);
...
return ngx_http_add_address(cf, cscf, port, lsopt);
}

-----------http/ngx_http.c:1281-----------------
static ngx_int_t
ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
...
addr = ngx_array_push(&port->addrs);
...
return ngx_http_add_server(cf, cscf, addr);
}

-----------http/ngx_http.c:1319--------------------
static ngx_int_t
ngx_http_add_server(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
ngx_http_conf_addr_t *addr)
{
...server = ngx_array_push(&addr->servers);
...
*server = cscf;
...
}

listen
指令的配置就这么就被解析并存储了。最后附一张 Nginx 监听地址和port的数据 结构组织图。





location
作用域

配置解析函数如今处于
server
作用域的上下文中: *
cf->ctx
指向
server
使用域创建的
ngx_http_conf_ctx_t
*
cf->module_type
值为
NGX_HTTP_MODULE
*
cf->cmd_type
值为
NGX_HTTP_SRV_CONF


解析函数碰到
location {}
后,会调用
location
指令的处理函数
ngx_http_core_location


    -------------http/ngx_http_core_module.c:271-----------
{ 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_conf_handler
里,从当前上下文中计算
location
指令处理函数的參数:

confp = *(void **) ((char *) cf->ctx + cmd->conf);

if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}

对上述代码的补充说明:

confp
指向了父作用域
server
中分配的
ngx_http_conf_ctx_t
srv_conf
变量。

conf
指向了
ngx_http_core_module
ngx_http_core_srv_conf_t
配置结构体。

conf
參数在
ngx_http_core_location
并未被使用。

接下来,
ngx_http_core_location
函数被调用了

-----------http/ngx_http_core_module.c:2391-----------
static char *
ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
...
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
...
pctx = cf->ctx;
ctx->main_conf = pctx->main_conf;
ctx->srv_conf = pctx->srv_conf;

ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
...
clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
clcf->loc_conf = ctx->loc_conf;
...
pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
...

ngx_http_add_location(cf, &pclcf->locations, clcf);

save = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTT_LOC_CONF;

rc = ngx_conf_parse(cf, NULL);

*cf = save
...
}

上面这段代码建立的关系能够參考本文提供的配置结构全图。

其他普通指令的解析,和上面的流程大同小异,就不再赘述了。

到这里为止,整个
http
作用域的配置文件解析过程就算是完毕了。在配置文件解析完毕 后,
ngx_http_block
还会做一系列其他工作,比方对全部模块的三类配置结构体进行初 始化、最内层作用域继承外层作用域的同一个指令值、又一次整理
location
、初始化
phase
,初始化 Nginx 同意的 HTTP 请求包头等等工作。

作用域继承

前面已经介绍过了,
http
作用域是能够嵌套的。同一时候,
NGX_HTTP_MODULE
类型模块的 大部分指令也是能够同一时候用于
http
和其子作用域 (
server
,
location
)中的。所以, 内层子作用域,须要从父作用域继承配置项。

这个行为最后达到的结果就是,出如今
NGX_HTTP_MAIN_CONF
中的指令配置项,在
NGX_HTTP_SRV_CONF
NGX_HTTP_LOC_CONF
中依旧能够发挥作用 (子作用域未显式配 置该指令时)。同一时候,子作用域里的配置项,能够覆盖父作用域里的配置项。

这个目标的达成,是由
ngx_http_block
函数完毕的。在继续进行之前,先拉回思绪。注 意此时的配置解析上下文:

* `cf->ctx` 指向 `NGX_HTTP_MAIN_CONF` 作用域的 `ngx_http_conf_ctx_t`
* `cf->module_type` 值为 `NGX_HTTP_MODULE`
* `cf->cmd_type` 值为 `NGX_HTTP_MAIN_CONF`

于是,在解析完整个
http
作用域的指令后,

------------http/ngx_http.c:246-------------
cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;

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->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
...
}

for (s = 0; s < cmcf->servers.nelts; s++) {
if (module->merge_srv_conf) {
rv = module->merge_srv_conf(cf, ctx->srv_conf[mi],
cscfp[s->ctx->srv_conf[mi]);
...
}

if (module->merge_loc_conf) {
rv = module->merge_loc_conf(cf, ctx->loc_conf[mi],
cscfp[s]->ctx->loc_conf[mi]);
...
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, mi);
}
}
}

对上述代码的补充说明:

首先,參照上面的结构图

代码仅仅对
main_conf
作用域模块配置数组中每一个模块的配置结构体进行了初始化。这 是由于,这些结构体在执行时,都会被用到。而
srv_conf

loc_conf
里面的配置结 构体在执行时并不会被直接用到,假设当中有些模块的配置项在解析过程中被赋值的话,下 面的合并操作,会把这些已经赋值的配置项都拷贝至子作用域的相应位置。

merge
操作的流程是:假设子作用域某配置项在解析过程中未被赋值,则将父作用域的 同样的配置项值拷贝至此配置项里;假设子作用域配置项在解析过程中被赋值了,则保留原 样;假设子作用域配置项和父作用域配置项都没有被初始化,则填入代码中预设的默认值。

以下选取
ngx_http_core_module
的相关函数代码对上述结论进行验证:

----------------http/ngx_http_core_module.c:2751-------------
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;
}
...
}

---------------http/ngx_http_core_module.c:2821-------------
static char *
ngx_http_core_merge_srv_conf(ngx_conf_t 8cf, void *parent, void *child)
{
ngx_http_core_srv_conf_t *prev = parent;
ngx_http_core_srv_conf_t *conf = child;
...
ngx_conf_merge_size_value(conf->connection_pool_size,
prev->connection_pool_siez, 256);
...
}

#define ngx_conf_merge_size_value(conf, prev, default)          \
if (conf == NGX_CONF_UNSET_SIZE) {                          \
conf = (prev == NGX_CONF_UNSET_SIZE) ? default : prev;  \
}

至此为止,作用域的继承是怎样实现的也介绍完了。

最后的整理

上面已经提到了,在
http
作用域解析完毕后,
ngx_http_block
还会继续完毕一系统 初始化工作,比方将配置中出现的
location
组织成能高速訪问的结构、初始化请求包头 结构体、初始化
phase
訪问结构等等。

这些基本上都能够独立开辟一篇文章进行分析,这里就先略过了。

请求来了

当一个
HTTP
请求到达 Nginx 时,什么阶段该使用哪个作用域的配置,配置又是怎么得 到的呢?

request
初始化函数
ngx_http_init_request
会被一个新创建的
request
配置 上下文做一次初始化 (此时,还无法推断此请求究竟属于哪个
server
作用域,先选 用默认
server
作用域):

---------------http/ngx_http_request.c:384------------
cscf = addr_conf->default_server;

r->main_conf = cscf->ctx->main_conf;
r->src_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
...

在读完
HTTP
请求包头,可以推断请求应该由哪个
server
作用域处理时,这时须要调 整一下
request
的配置上下文:

-------------http/ngx_http_request.c:1688-------------
static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
...
r->srv_conf = cscf->ctx->srv_conf;
r->loc_conf = cscf->ctx->loc_conf;
...
}

此时,
r->loc_conf
指向
server
作用域的
loc_conf
,在确定了
request
的 所属的
location
后,会接着调整
r->loc_conf
的指向。

同一时候,内部
request
,rewrite 的
request
,被重定向的
request
都要通过改变
r->loc_conf
的指向来实现同一个
request
在不同
location
作用域跳转的功能。

另外,配置结构的获取,主要通过以下的宏实现:

#define ngx_http_get_module_main_conf(r, module)                             \
(r)->main_conf[module.ctx_index]
#define ngx_http_get_module_srv_conf(r, module)  (r)->srv_conf[module.ctx_index]
#define ngx_http_get_module_loc_conf(r, module)  (r)->loc_conf[module.ctx_index]

#define ngx_http_conf_get_module_main_conf(cf, module)                        \
((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
#define ngx_http_conf_get_module_srv_conf(cf, module)                         \
((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
#define ngx_http_conf_get_module_loc_conf(cf, module)                         \
((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: