/ 中存储网

Nginx配置文件解析相关代码分析

2013-08-18 20:36:57 来源:IT技术网

Nginx的配置解析相关的部分比较绕,比如为何要有4重指针,比如NGX_MAIN_CONF , loc_conf,NGX_DIRECT_CONF有什么区别呢?这些我前面的blog都有些涉及,这次主要是把配置这块完全拿出来然后来分析下。

首先来看配置解析时的数据结构,这里主要是ngx_conf_t,这个结构保存了解析配置文件所需要的一些域,这个是非常重要的一个数据结构,我们详细来看这个结构:

struct ngx_conf_s {

//当前解析到的命令名

char                 *name;

//当前命令的所有参数

ngx_array_t          *args;

//使用的cycle

ngx_cycle_t          *cycle;

//所使用的内存池

ngx_pool_t           *pool;

//这个pool将会在配置解析完毕后释放。

ngx_pool_t           *temp_pool;

//这个表示将要解析的配置文件

ngx_conf_file_t      *conf_file;

//配置log

ngx_log_t            *log;

//主要为了提供模块的层次化(后续会详细介绍)

void                 *ctx;

//模块类型

ngx_uint_t            module_type;

//命令类型

ngx_uint_t            cmd_type;

//模块自定义的handler

ngx_conf_handler_pt   handler;

//自定义handler的conf

char                 *handler_conf;

};

上面的有些域可能现在还是不太理解,不过没关系,接下来就会一个个的分析到。

我们来看配置解析的入口,入口在ngx_init_cycle中,这里比较简单,就是设置ngx_conf_t 然后传递给ngx_conf_parse解析。

//创建conf_ctx

cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));

if (cycle->conf_ctx == NULL) {

ngx_destroy_pool(pool);

return NULL;

}

.............................................

for (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);

if (rv == NULL) {

ngx_destroy_pool(pool);

return NULL;

}

//这里看到conf_ctx里面就是放对应模块的main conf.

cycle->conf_ctx[ngx_modules[i]->index] = rv;

}

}

.................................

//初始化conf

conf.ctx = cycle->conf_ctx;

conf.cycle = cycle;

conf.pool = pool;

conf.log = log;

//注意,一开始命令的类型就是MAIN,并且模块类型是core。

conf.module_type = NGX_CORE_MODULE;

conf.cmd_type = NGX_MAIN_CONF;

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;

}

然后来看ngx_conf_parse,这个函数第二个是将要解析的文件名,不过这里还有一个要注意的,那就是第二个参数可以为空的,如果为空,则说明将要解析的是block中的内容或者param。

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) {

/* open configuration file */

................................................

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

//到这里说明接下来解析的是block中的内容

type = parse_block;

} else {

//参数

type = parse_param;

}

for ( ;; ) {

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_OK || rc == NGX_CONF_BLOCK_START */

//如果有handler,则调用handle

if (cf->handler) {

/*

* the custom handler, i.e., that is used in the http's

* "types { ... }" directive

*/

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;

}

//没有handler则调用默认解析函数

rc = ngx_conf_handler(cf, rc);

if (rc == NGX_ERROR) {

goto failed;

}

}

failed:

rc = NGX_ERROR;

done:

....................................

return NGX_CONF_OK;

}

在看ngx_conf_handler之前我们先来看Nginx的配置文件的结构,以及为什么要有cf->handler。

一般的一个Nginx配置文件是这样子的:

worker_processes 1;

error_log logs/error.log;

pid logs/nginx.pid;

events {

worker_connections 1024;

}

http {

include mime.types;

default_type application/octet-stream;

sendfile on;

keepalive_timeout 65;

#gzip on;

server {

listen 80;

server_name localhost;

access_log logs/host.access.log main;

location / {

root html;

index index.html index.htm;

}

error_page 500 502 503 504 /50x.html;

location = /50x.html {

root html;

}

}

}

可以看到Nginx的配置文件是分块的,然后event, http都是一个大的core 模块,然后core模块中包含了很多2级模块(epoll/kqeue/proxy..).也就是1级模块中必须包含一个上下文用来保存2级模块的配置。而在HTTP模块中又有一些特殊,那就是HTTP模块中每个指令都可能会有3个作用域,那就是main/server/loc,所以在HTTP的上下文中,必须同时保存这3个上下文。

然后我们来看Nginx中的命令都有那些类型:

#define NGX_DIRECT_CONF      0x00010000

#define NGX_MAIN_CONF        0x01000000

#define NGX_ANY_CONF         0x0F000000

#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

#define NGX_MAIL_MAIN_CONF      0x02000000

#define NGX_MAIL_SRV_CONF       0x04000000

Nginx中的参数类型有这么多种其中最有必要区分的就是第一种和第二种,一般来说DIRECT_CONF和MAIN_CONF是同时使用的,也就是有第一个就有第二个。DIRECT_CONF顾名思义,就是说直接存取CONF,也就是说进入命令解析函数的同时,CONF已经创建好了,只需要直接使用就行了(也就是会有create_conf回调)。而Main_conf就是说最顶层的conf,比如HTTP/EVENT/PID等等,可以看到都属属于CORE 模块。而NGX_HTTP_XXX就是所有HTTP模块的子模块.

理解了Nginx配置的基本结构,我们来看ngx_conf_handler,这个函数以前介绍过,这次这里就只关注最核心的部分,下面这部分是遍历命令表,然后找到了对应的命令,然后进行处理:

//如果设置了type

if (!(cmd->type & NGX_CONF_ANY)) {

//首先判断参数个数是否合法

if (cmd->type & NGX_CONF_FLAG) {

if (cf->args->nelts != 2) {

goto invalid;

}

} else if (cmd->type & NGX_CONF_1MORE) {

if (cf->args->nelts < 2) {

goto invalid;

}

.................................................

}

/* set up the directive's configuration context */

conf = NULL;

//最核心的地方,

if (cmd->type & NGX_DIRECT_CONF) {

我们还记得最开始ctx是包含了所有core模块的conf(create_conf回调),因此这里取出对应的模块conf.

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

} else if (cmd->type & NGX_MAIN_CONF) {

//如果不是DIRECT_CONF并且是MAIN,则说明我们需要在配置中创建自己模块的上下文(也就是需要进入二级模块)

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);

if (rv == NGX_CONF_OK) {

return NGX_OK;

}

if (rv == NGX_CONF_ERROR) {

return NGX_ERROR;

}

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

""%s" directive %s", name->data, rv);

return NGX_ERROR;

}

上面代码中二级模块解析那部分先放一下,首先来看Nginx中带二级模块的一级模块如何解析命令的,来看HTTP模块(event模块基本一样)的解析代码。

//可以看到没有direct_conf,因为http包含有二级模块。

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

};

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;

/* the main http context */

ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));

if (ctx == NULL) {

return NGX_CONF_ERROR;

}

//最核心的地方,可以看到修改了传递进来的conf

*(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 main_conf context, it is the same in the all http contexts */

//创建HTTP对应的conf,因为每个级别(main/ser/loc)都会包含模块的conf.

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

*/

....................................

//保存当前使用的cf,因为我们只是在解析HTTP时需要改变当前的cf,

pcf = *cf;

//保存当前模块的上下文

cf->ctx = ctx;

..........................................

/* parse inside the http{} block */

//设置模块类型和命令类型

cf->module_type = NGX_HTTP_MODULE;

cf->cmd_type = NGX_HTTP_MAIN_CONF;

//开始解析,这里注意传递进去的文件名是空

rv = ngx_conf_parse(cf, NULL);

if (rv != NGX_CONF_OK) {

goto failed;

}

/*

* init http{} main_conf's, merge the server{}s' srv_conf's

* and its location{}s' loc_conf's

*/

.........................................

/*

* http{}'s cf->ctx was needed while the configuration merging

* and in postconfiguration process

*/

//回复cf

*cf = pcf;

......................................

return NGX_CONF_OK;

failed:

*cf = pcf;

return rv;

}

这里有个非常关键的地方,那就是在每个级别都会保存对应的ctx(main/ser/loc),怎么说呢,就是在解析HTTP main中创建了3个ctx(main/srv/loc),而在HTTP srv block中将会创建2个ctx(main/srv/loc),或许会问重复了怎么办?重复了,那就需要merge了。比如一个命令(srv_offset)在HTTP main中有一个,那么Nginx将会把它放入到HTTP main的ctx的srv ctx中,然后server block也有一个,那么Nginx会继续把它放到Server ctx的 srv_conf中,最后merge他们。

因此我们来看server这个命令的解析:

{ ngx_string("server"),

NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_MULTI|NGX_CONF_NOARGS,

ngx_http_core_server,

0,

0,

NULL },

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;

//main conf不变

ctx->main_conf = http_ctx->main_conf;

/* the server{}'s srv_conf */

//创建新的srv和loc 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;

}

............................

/* the server configuration context */

cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];

cscf->ctx = ctx;

cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

//保存所有的servers,可以看到是保存在main中的。这样子最后在HTTP main中就可以取到这个srv conf.

cscfp = ngx_array_push(&cmcf->servers);

if (cscfp == NULL) {

return NGX_CONF_ERROR;

}

*cscfp = cscf;

/* parse inside server{} */

//解析,可以看到设置type为srv_conf.

pcf = *cf;

cf->ctx = ctx;

cf->cmd_type = NGX_HTTP_SRV_CONF;

rv = ngx_conf_parse(cf, NULL);

//恢复cf.

*cf = pcf;

  ........................

}

return rv;

}

了解了这些,我们来看最上面的那段代码:

struct ngx_command_s {

ngx_str_t             name;

ngx_uint_t            type;

char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

//conf就是对应的上下文偏移.比如NGX_HTTP_LOC_CONF_OFFSET

ngx_uint_t            conf;

ngx_uint_t            offset;

void                 *post;

};

............................

else if (cf->ctx) {

//取得对应的1级模块的二级上下文(HTTP的 srv_offset)

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

if (confp) {

//然后取出对应的模块conf.

conf = confp[ngx_modules[i]->ctx_index];

}

}

然后来看一些简单的命令是如何使用和配置的。在看之前先来看几个核心的结构:

typedef struct {

void        **main_conf;

void        **srv_conf;

void        **loc_conf;

} ngx_http_conf_ctx_t;

//下面这些就是放到ngx_command_t的conf域,可以看到就是对应conf的偏移.

#define NGX_HTTP_MAIN_CONF_OFFSET  offsetof(ngx_http_conf_ctx_t, main_conf)

#define NGX_HTTP_SRV_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, srv_conf)

#define NGX_HTTP_LOC_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, loc_conf)

//下面就是如何来取模块的配置

#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_http_cycle_get_module_main_conf(cycle, module)                    

(cycle->conf_ctx[ngx_http_module.index] ?                                 

((ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index])      

->main_conf[module.ctx_index]:                                    

NULL)

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]

来看几个典型的配置命令。

首先是env,它是一个 DIRECT_CONF命令.

//可以看到有create_conf函数

static ngx_core_module_t  ngx_core_module_ctx = {

ngx_string("core"),

ngx_core_module_create_conf,

ngx_core_module_init_conf

};

................................

{ ngx_string("env"),

NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,

ngx_set_env,

0,

0,

NULL },

static char *

ngx_set_env(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

//直接读取到然后使用

ngx_core_conf_t  *ccf = conf;

.............................

return NGX_CONF_OK;

}

然后是http的root命令:

{ ngx_string("root"),

NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF

|NGX_CONF_TAKE1,

ngx_http_core_root,

NGX_HTTP_LOC_CONF_OFFSET,

0,

NULL },

static char *

ngx_http_core_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

//也是直接使用(通过传递进入的偏移NGX_HTTP_LOC_CONF_OFFSET)

ngx_http_core_loc_conf_t *clcf = conf;

.......................................

return NGX_CONF_OK;

}

最后来看一下为什么要有四级指针,这个其实是和模块级别相关的,如果只有一级模块,那么只需要2级指针就够了,可是现在还有2级模块,那么每个1级模块的2级指针里面必须得扩展指针以保存本级别模块的上下文,那么自然就是4级指针了,详细可以看看event模块,它里面比较清晰。