Nginx的配置解析相关的部分比较绕,比如为何要有4重指针,比如NGX_MAIN_CONF ,loc_conf,NGX_DIRECT_CONF有什么区别呢?这些我前面的blog都有些涉及,这次主要是把配置这块完全拿出来然后来分析下。
首先来看配置解析时的数据结构,这里主要是ngx_conf_t,这个结构保存了解析配置文件所需要的一些域,这个是非常重要的一个数据结构,我们详细来看这个结构:
序列号 | CPU | RAM | HDD | 带宽 | 售价(美元) | 免费试用 |
---|---|---|---|---|---|---|
香港服务器1 | E5-2620 | 32G | 1T HDD | 50M/无限流量 | $196.00 | 立即申请 |
香港服务器2 | E5-2650 | 32G | 1T HDD | 50M/无限流量 | $256.00 | 立即申请 |
香港服务器3 | E5-2680 | 32G | 1T HDD | 50M/无限流量 | $316.00 | 立即申请 |
香港服务器4 | E5-2690 | 32G | 1T HDD | 50M/无限流量 | $336.00 | 立即申请 |
香港服务器5 | E5-2697 | 32G | 1T HDD | 50M/无限流量 | $376.00 | 立即申请 |
香港服务器6 | E5-2620*2 | 32G | 1T HDD | 50M/无限流量 | $376.00 | 立即申请 |
香港服务器7 | E5-2650*2 | 32G | 1T HDD | 50M/无限流量 | $436.00 | 立即申请 |
香港服务器8 | E5-2680*2 | 32G | 1T HDD | 50M/无限流量 | $476.00 | 立即申请 |
香港服务器9 | E5-2690*2 | 32G | 1T HDD | 50M/无限流量 | $556.00 | 立即申请 |
香港服务器10 | E5-2697*2 | 32G | 1T HDD | 50M/无限流量 | $596.00 | 立即申请 |
香港服务器11 | E5-2680v4*2 | 32G | 1T HDD | 50M/无限流量 | $696.00 | 立即申请 |
香港服务器12 | E5-2698v4*2 | 32G | 1T HDD | 50M/无限流量 | $796.00 | 立即申请 |
structngx_conf_s{//当前解析到的命令名char*name;//当前命令的所有参数ngx_array_t*args;//使用的cyclengx_cycle_t*cycle;//所使用的内存池ngx_pool_t*pool;//这个pool将会在配置解析完毕后释放。ngx_pool_t*temp_pool;//这个表示将要解析的配置文件ngx_conf_file_t*conf_file;//配置logngx_log_t*log;//主要为了提供模块的层次化(后续会详细介绍)void*ctx;//模块类型ngx_uint_tmodule_type;//命令类型ngx_uint_tcmd_type;//模块自定义的handlerngx_conf_handler_pthandler;//自定义handler的confchar*handler_conf;};
上面的有些域可能现在还是不太理解,不过没关系,接下来就会一个个的分析到。
我们来看配置解析的入口,入口在ngx_init_cycle中,这里比较简单,就是设置ngx_conf_t然后传递给ngx_conf_parse解析。
//创建conf_ctxcycle->conf_ctx=ngx_pcalloc(pool,ngx_max_module*sizeof(void*));if(cycle->conf_ctx==NULL){ngx_destroy_pool(pool);returnNULL;}.............................................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);returnNULL;}//这里看到conf_ctx里面就是放对应模块的mainconf.cycle->conf_ctx[ngx_modules[i]->index]=rv;}}.................................//初始化confconf.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);returnNULL;}//开始解析文件if(ngx_conf_parse(&conf,&cycle->conf_file)!=NGX_CONF_OK){environ=senv;ngx_destroy_cycle_pools(&conf);returnNULL;}
然后来看ngx_conf_parse,这个函数第二个是将要解析的文件名,不过这里还有一个要注意的,那就是第二个参数可以为空的,如果为空,则说明将要解析的是block中的内容或者param。
char*ngx_conf_parse(ngx_conf_t*cf,ngx_str_t*filename){char*rv;ngx_fd_tfd;ngx_int_trc;ngx_buf_tbuf;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;#endifif(filename){/*openconfigurationfile*/................................................}elseif(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()mayreturn**NGX_ERRORthereiserror*NGX_OKthetokenterminatedby";"wasfound*NGX_CONF_BLOCK_STARTthetokenterminatedby"{"wasfound*NGX_CONF_BLOCK_DONEthe"}"wasfound*NGX_CONF_FILE_DONEtheconfigurationfileisdone*/...................................................../*rc==NGX_OK||rc==NGX_CONF_BLOCK_START*///如果有handler,则调用handleif(cf->handler){/**thecustomhandler,i.e.,thatisusedinthehttp's*"types{...}"directive*/rv=(*cf->handler)(cf,NULL,cf->handler_conf);if(rv==NGX_CONF_OK){continue;}if(rv==NGX_CONF_ERROR){gotofailed;}ngx_conf_log_error(NGX_LOG_EMERG,cf,0,rv);gotofailed;}//没有handler则调用默认解析函数rc=ngx_conf_handler(cf,rc);if(rc==NGX_ERROR){gotofailed;}}failed:rc=NGX_ERROR;done:....................................returnNGX_CONF_OK;}
在看ngx_conf_handler之前我们先来看Nginx的配置文件的结构,以及为什么要有cf->handler。
一般的一个Nginx配置文件是这样子的:
worker_processes1;
error_loglogs/error.log;
pidlogs/nginx.pid;
events{worker_connections1024;}
http{includemime.types;default_typeapplication/octet-stream;
sendfileon;
keepalive_timeout65;
#gzipon;
server{listen80;server_namelocalhost;
access_loglogs/host.access.logmain;
location/{roothtml;indexindex.htmlindex.htm;}
error_page500502503504/50x.html;location=/50x.html{roothtml;}}
}
可以看到Nginx的配置文件是分块的,然后event, http都是一个大的core模块,然后core模块中包含了很多2级模块(epoll/kqeue/proxy..).也就是1级模块中必须包含一个上下文用来保存2级模块的配置。而在HTTP模块中又有一些特殊,那就是HTTP模块中每个指令都可能会有3个作用域,那就是main/server/loc,所以在HTTP的上下文中,必须同时保存这3个上下文。
然后我们来看Nginx中的命令都有那些类型:
#defineNGX_DIRECT_CONF0x00010000#defineNGX_MAIN_CONF0x01000000#defineNGX_ANY_CONF0x0F000000#defineNGX_EVENT_CONF0x02000000#defineNGX_HTTP_MAIN_CONF0x02000000#defineNGX_HTTP_SRV_CONF0x04000000#defineNGX_HTTP_LOC_CONF0x08000000#defineNGX_HTTP_UPS_CONF0x10000000#defineNGX_HTTP_SIF_CONF0x20000000#defineNGX_HTTP_LIF_CONF0x40000000#defineNGX_HTTP_LMT_CONF0x80000000#defineNGX_MAIL_MAIN_CONF0x02000000#defineNGX_MAIL_SRV_CONF0x04000000
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,这个函数以前介绍过,这次这里就只关注最核心的部分,下面这部分是遍历命令表,然后找到了对应的命令,然后进行处理:
//如果设置了typeif(!(cmd->type&NGX_CONF_ANY)){//首先判断参数个数是否合法if(cmd->type&NGX_CONF_FLAG){if(cf->args->nelts!=2){gotoinvalid;}}elseif(cmd->type&NGX_CONF_1MORE){if(cf->args->nelts<2){gotoinvalid;}.................................................}/*setupthedirective'sconfigurationcontext*/conf=NULL;//最核心的地方,if(cmd->type&NGX_DIRECT_CONF){我们还记得最开始ctx是包含了所有core模块的conf(create_conf回调),因此这里取出对应的模块conf.conf=((void**)cf->ctx)[ngx_modules[i]->index];}elseif(cmd->type&NGX_MAIN_CONF){//如果不是DIRECT_CONF并且是MAIN,则说明我们需要在配置中创建自己模块的上下文(也就是需要进入二级模块)conf=&(((void**)cf->ctx)[ngx_modules[i]->index]);}elseif(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){returnNGX_OK;}if(rv==NGX_CONF_ERROR){returnNGX_ERROR;}ngx_conf_log_error(NGX_LOG_EMERG,cf,0,""%s"directive%s",name->data,rv);returnNGX_ERROR;}
上面代码中二级模块解析那部分先放一下,首先来看Nginx中带二级模块的一级模块如何解析命令的,来看HTTP模块(event模块基本一样)的解析代码。
//可以看到没有direct_conf,因为http包含有二级模块。staticngx_command_tngx_http_commands[]={{ngx_string("http"),NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,ngx_http_block,0,0,NULL},ngx_null_command};staticchar*ngx_http_block(ngx_conf_t*cf,ngx_command_t*cmd,void*conf){char*rv;ngx_uint_tmi,m,s;ngx_conf_tpcf;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;/*themainhttpcontext*/ctx=ngx_pcalloc(cf->pool,sizeof(ngx_http_conf_ctx_t));if(ctx==NULL){returnNGX_CONF_ERROR;}//最核心的地方,可以看到修改了传递进来的conf*(ngx_http_conf_ctx_t**)conf=ctx;/*countthenumberofthehttpmodulesandsetuptheirindices*/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++;}/*thehttpmain_confcontext,itisthesameintheallhttpcontexts*///创建HTTP对应的conf,因为每个级别(main/ser/loc)都会包含模块的conf.ctx->main_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);if(ctx->main_conf==NULL){returnNGX_CONF_ERROR;}/**thehttpnullsrv_confcontext,itisusedtomerge*theserver{}s'srv_conf's*/ctx->srv_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);if(ctx->srv_conf==NULL){returnNGX_CONF_ERROR;}/**thehttpnullloc_confcontext,itisusedtomerge*theserver{}s'loc_conf's*/ctx->loc_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);if(ctx->loc_conf==NULL){returnNGX_CONF_ERROR;}/**createthemain_conf's,thenullsrv_conf's,andthenullloc_conf's*oftheallhttpmodules*/....................................//保存当前使用的cf,因为我们只是在解析HTTP时需要改变当前的cf,pcf=*cf;//保存当前模块的上下文cf->ctx=ctx;........................................../*parseinsidethehttp{}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){gotofailed;}/**inithttp{}main_conf's,mergetheserver{}s'srv_conf's*anditslocation{}s'loc_conf's*/........................................./**http{}'scf->ctxwasneededwhiletheconfigurationmerging*andinpostconfigurationprocess*///回复cf*cf=pcf;......................................returnNGX_CONF_OK;failed:*cf=pcf;returnrv;}
这里有个非常关键的地方,那就是在每个级别都会保存对应的ctx(main/ser/loc),怎么说呢,就是在解析HTTPmain中创建了3个ctx(main/srv/loc),而在HTTP srvblock中将会创建2个ctx(main/srv/loc),或许会问重复了怎么办?重复了,那就需要merge了。比如一个命令(srv_offset)在HTTPmain中有一个,那么Nginx将会把它放入到HTTP main的ctx的srv ctx中,然后serverblock也有一个,那么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},staticchar*ngx_http_core_server(ngx_conf_t*cf,ngx_command_t*cmd,void*dummy){char*rv;void*mconf;ngx_uint_ti;ngx_conf_tpcf;ngx_http_module_t*module;structsockaddr_in*sin;ngx_http_conf_ctx_t*ctx,*http_ctx;ngx_http_listen_opt_tlsopt;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){returnNGX_CONF_ERROR;}http_ctx=cf->ctx;//mainconf不变ctx->main_conf=http_ctx->main_conf;/*theserver{}'ssrv_conf*///创建新的srv和locconf.ctx->srv_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);if(ctx->srv_conf==NULL){returnNGX_CONF_ERROR;}/*theserver{}'sloc_conf*/ctx->loc_conf=ngx_pcalloc(cf->pool,sizeof(void*)*ngx_http_max_module);if(ctx->loc_conf==NULL){returnNGX_CONF_ERROR;}............................/*theserverconfigurationcontext*/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中的。这样子最后在HTTPmain中就可以取到这个srvconf.cscfp=ngx_array_push(&cmcf->servers);if(cscfp==NULL){returnNGX_CONF_ERROR;}*cscfp=cscf;/*parseinsideserver{}*///解析,可以看到设置type为srv_conf.pcf=*cf;cf->ctx=ctx;cf->cmd_type=NGX_HTTP_SRV_CONF;rv=ngx_conf_parse(cf,NULL);//恢复cf.*cf=pcf;........................}returnrv;}
了解了这些,我们来看最上面的那段代码:
structngx_command_s{ngx_str_tname;ngx_uint_ttype;char*(*set)(ngx_conf_t*cf,ngx_command_t*cmd,void*conf);//conf就是对应的上下文偏移.比如NGX_HTTP_LOC_CONF_OFFSETngx_uint_tconf;ngx_uint_toffset;void*post;};............................elseif(cf->ctx){//取得对应的1级模块的二级上下文(HTTP的srv_offset)confp=*(void**)((char*)cf->ctx+cmd->conf);if(confp){//然后取出对应的模块conf.conf=confp[ngx_modules[i]->ctx_index];}}
然后来看一些简单的命令是如何使用和配置的。在看之前先来看几个核心的结构:
typedefstruct{void**main_conf;void**srv_conf;void**loc_conf;}ngx_http_conf_ctx_t;//下面这些就是放到ngx_command_t的conf域,可以看到就是对应conf的偏移.#defineNGX_HTTP_MAIN_CONF_OFFSEToffsetof(ngx_http_conf_ctx_t,main_conf)#defineNGX_HTTP_SRV_CONF_OFFSEToffsetof(ngx_http_conf_ctx_t,srv_conf)#defineNGX_HTTP_LOC_CONF_OFFSEToffsetof(ngx_http_conf_ctx_t,loc_conf)//下面就是如何来取模块的配置#definengx_http_get_module_main_conf(r,module)(r)->main_conf[module.ctx_index]#definengx_http_get_module_srv_conf(r,module)(r)->srv_conf[module.ctx_index]#definengx_http_get_module_loc_conf(r,module)(r)->loc_conf[module.ctx_index]#definengx_http_conf_get_module_main_conf(cf,module)((ngx_http_conf_ctx_t*)cf->ctx)->main_conf[module.ctx_index]#definengx_http_conf_get_module_srv_conf(cf,module)((ngx_http_conf_ctx_t*)cf->ctx)->srv_conf[module.ctx_index]#definengx_http_conf_get_module_loc_conf(cf,module)((ngx_http_conf_ctx_t*)cf->ctx)->loc_conf[module.ctx_index]#definengx_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)#definengx_get_conf(conf_ctx,module)conf_ctx[module.index]
来看几个典型的配置命令。
首先是env,它是一个 DIRECT_CONF命令.
//可以看到有create_conf函数staticngx_core_module_tngx_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},staticchar*ngx_set_env(ngx_conf_t*cf,ngx_command_t*cmd,void*conf){//直接读取到然后使用ngx_core_conf_t*ccf=conf;.............................returnNGX_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},staticchar*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;.......................................returnNGX_CONF_OK;}
最后来看一下为什么要有四级指针,这个其实是和模块级别相关的,如果只有一级模块,那么只需要2级指针就够了,可是现在还有2级模块,那么每个1级模块的2级指针里面必须得扩展指针以保存本级别模块的上下文,那么自然就是4级指针了,详细可以看看event模块,它里面比较清晰。