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配置文件是这样子的:
可以看到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模块,它里面比较清晰。 (责任编辑:IT) |