Nginx 配置解析
在上一篇文章《 Nginx 启动初始化过程》简单介绍了 Nginx 启动的过程,并分析了其启动过程的源码。在启动过程中有一个步骤非常重要,就是调用函数 ngx_init_cycle(),该函数的调用为配置解析提供了接口。配置解析接口大概可分为两个阶段:准备数据阶段和配置解析阶段;
准备数据阶段包括:
- 准备内存;
- 准备错误日志;
- 准备所需数据结构;
配置解析阶段是调用函数:
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
struct ngx_conf_s {
char *name;
ngx_array_t *args;
ngx_cycle_t *cycle;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
ngx_conf_file_t *conf_file;
ngx_log_t *log;
void *ctx;
ngx_uint_t module_type;
ngx_uint_t cmd_type;
ngx_conf_handler_pt handler;
char *handler_conf;
};
conf_file 是存放 Nginx 配置文件的相关信息。ngx_conf_file_t 结构体的定义如下:
typedef struct {
ngx_file_t file;
ngx_buf_t *buffer;
ngx_uint_t line;
} ngx_conf_file_t;
Nginx 的配置文件是分块配置的,常见的有http 块、server 块、location 块以及upsteam 块和 mail 块等。每一个这样的配置块代表一个作用域。高一级配置块的作用域包含了多个低一级配置块的作用域,也就是有作用域嵌套的现象。这样,配置文件中的许多指令都会同时包含在多个作用域内。比如,http 块中的指令都可能同时处于http 块、server 块和location 块等三层作用域内。
在 Nginx 程序解析配置文件时,每一条指令都应该记录自己所属的作用域范围,而配置文件上下文ctx 变量就是用来存放当前指令所属的作用域的。在Nginx 配置文件的各种配置块中,http 块可以包含子配置块,这在存储结构上比较复杂。
#define NGX_DIRECT_CONF 0x00010000
#define NGX_MAIN_CONF 0x01000000
#define NGX_ANY_CONF 0x0F000000
这些是 core 类型模块支持的指令类型。其中的 NGX_DIRECT_CONF类指令在 Nginx 程序进入配置解析函数之前已经初始化完成,所以在进入配置解析函数之后可以将它们直接解析并存储到实际的数据 结构中,从配置文件的结构上来看,它们一般指的就是那些游离于配置块之外、处于配置文件全局块部分的指令。NGX_MAIN_CONF 类指令包括event、http、mail、upstream 等可以形成配置块的指令,它们没有自己的初始化函数。Nginx 程序在解析配置文件时如果遇到 NGX_MAIN_CONF 类指令,将转入对下一级指令的解析。
以下是 event 类型模块支持的指令类型。
#define NGX_EVENT_CONF 0x02000000
#define NGX_HTTP_MAIN_CONF 0x02000000
#define NGX_HTTP_SRV_CONF 0x04000000
#define NGX_HTTP_LOC_CONF 0x08000000
#define NGX_HTTP_UPS_CONF 0x10000000
#define NGX_HTTP_SIF_CONF 0x20000000
#define NGX_HTTP_LIF_CONF 0x40000000
#define NGX_HTTP_LMT_CONF 0x80000000
配置解析模块在 src/core/ngx_conf_file.c 中实现。模块提供的接口函数主要是ngx_conf_parse。另外,模块提供另一个单独的接口ngx_conf_param,用来解析命令行传递的配置,这个接口也是对ngx_conf_parse 的包装。首先看下配置解析函数 ngx_conf_parse,其定义如下:
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
char *rv;
ngx_fd_t fd;
ngx_int_t rc;
ngx_buf_t buf;
ngx_conf_file_t *prev, conf_file;
enum {
parse_file = 0,
parse_block,
parse_param
} type;
#if (NGX_SUPPRESS_WARN)
fd = NGX_INVALID_FILE;
prev = NULL;
#endif
if (filename) {
fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
if (fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%s\" failed",
filename->data);
return NGX_CONF_ERROR;
}
prev = cf->conf_file;
cf->conf_file = &conf_file;
if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
ngx_fd_info_n " \"%s\" failed", filename->data);
}
cf->conf_file->buffer = &buf;
buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
if (buf.start == NULL) {
goto failed;
}
buf.pos = buf.start;
buf.last = buf.start;
buf.end = buf.last + NGX_CONF_BUFFER;
buf.temporary = 1;
cf->conf_file->file.fd = fd;
cf->conf_file->file.name.len = filename->len;
cf->conf_file->file.name.data = filename->data;
cf->conf_file->file.offset = 0;
cf->conf_file->file.log = cf->log;
cf->conf_file->line = 1;
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);
if (rc == NGX_ERROR) {
goto done;
}
if (rc == NGX_CONF_BLOCK_DONE) {
if (type != parse_block) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
goto failed;
}
goto done;
}
if (rc == NGX_CONF_FILE_DONE) {
if (type == parse_block) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"unexpected end of file, expecting \"}\"");
goto failed;
}
goto done;
}
if (rc == NGX_CONF_BLOCK_START) {
if (type == parse_param) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"block directives are not supported "
"in -g option");
goto failed;
}
}
if (cf->handler) {
if (rc == NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"{\"");
goto failed;
}
rv = (*cf->handler)(cf, NULL, cf->handler_conf);
if (rv == NGX_CONF_OK) {
continue;
}
if (rv == NGX_CONF_ERROR) {
goto failed;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
goto failed;
}
rc = ngx_conf_handler(cf, rc);
if (rc == NGX_ERROR) {
goto failed;
}
}
failed:
rc = NGX_ERROR;
done:
if (filename) {
if (cf->conf_file->buffer->start) {
ngx_free(cf->conf_file->buffer->start);
}
if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " %s failed",
filename->data);
return NGX_CONF_ERROR;
}
cf->conf_file = prev;
}
if (rc == NGX_ERROR) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
从配置解析函数的源码可以看出,该函数分为两个阶段:语法分析和 指令解析。语法分析由 ngx_conf_read_token()函数完成。指令解析有两种方式:一种是Nginx 内建的指令解析机制;另一种是自定义的指令解析机制。自定义指令解析源码如下所示:
if (cf->handler) {
if (rc == NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"{\"");
goto failed;
}
rv = (*cf->handler)(cf, NULL, cf->handler_conf);
if (rv == NGX_CONF_OK) {
continue;
}
if (rv == NGX_CONF_ERROR) {
goto failed;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
goto failed;
}
而Nginx 内置解析机制有函数ngx_conf_handler() 实现。其定义如下: