Nginx的配置解析相关的部分比较绕,比如为何要有4重指针,比如NGX_MAIN_CONF , loc_conf,NGX_DIRECT_CONF有什么区别呢?这些我前面的blog都有些涉及,这次主要是把配置这块完全拿出来然后来分析下。
首先来看配置解析时的数据结构,这里主要是ngx_conf_t,这个结构保存了解析配置文件所需要的一些域,这个是非常重要的一个数据结构,我们详细来看这个结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 //创建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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 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中的命令都有那些类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #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,这个函数以前介绍过,这次这里就只关注最核心的部分,下面这部分是遍历命令表,然后找到了对应的命令,然后进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 //如果设置了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模块基本一样)的解析代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 //可以看到没有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这个命令的解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 { 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; }
了解了这些,我们来看最上面的那段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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]; } }
然后来看一些简单的命令是如何使用和配置的。在看之前先来看几个核心的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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命令.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 //可以看到有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命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { 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模块,它里面比较清晰。