摘要:配置文件解析一配置解析流程解析配置的入口函數(shù)是,其輸入?yún)?shù)表示配置文件路徑,如果為表明此時(shí)解析的是指令塊。函數(shù)邏輯比較簡(jiǎn)單,就是讀取完整指令,并調(diào)用函數(shù)處理指令。
運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè)
本文作為nginx配置文件解析的第二篇,開(kāi)始講解nginx配置文件解析的源碼,在閱讀本文之前,希望你已經(jīng)閱讀過(guò)第一篇?!秐ginx配置文件解析(一)》
1.1配置解析流程解析配置的入口函數(shù)是ngx_conf_parse(ngx_conf_t cf, ngx_str_t filename),其輸入?yún)?shù)filename表示配置文件路徑,如果為NULL表明此時(shí)解析的是指令塊。
那么cf是什么呢?先看看其結(jié)構(gòu)體聲明:
struct ngx_conf_s { char *name; //當(dāng)前讀取到的指令名稱(chēng) ngx_array_t *args; //當(dāng)前讀取到的指令參數(shù) ngx_cycle_t *cycle; //指向全局cycle ngx_pool_t *pool; //內(nèi)存池 ngx_conf_file_t *conf_file; //配置文件 void *ctx; //上下文 ngx_uint_t module_type; //模塊類(lèi)型 ngx_uint_t cmd_type; //指令類(lèi)型 ngx_conf_handler_pt handler; //一般都是NULL,暫時(shí)不管 };
重點(diǎn)需要關(guān)注這些字段:
1)name和args存儲(chǔ)當(dāng)前讀取到的指令信息;
2)ctx上下文,就是我們上面所說(shuō)的指令上下文,想象下如果沒(méi)有ctx我們獲取該指令最終存儲(chǔ)的位置;
3)module_type和cmd_type分別表示模塊類(lèi)型與指令類(lèi)型;讀取到某條指令時(shí),需要遍歷所有模塊的指令數(shù)組,通過(guò)這兩個(gè)字段可以過(guò)濾某些不應(yīng)該解析該配置的模塊與指令。
函數(shù)ngx_conf_parse邏輯比較簡(jiǎn)單,就是讀取完整指令,并調(diào)用函數(shù)ngx_conf_handler處理指令。
函數(shù)ngx_conf_handler主要邏輯是,遍歷類(lèi)型為cf->module_type的模塊,查找該模塊指令數(shù)組中類(lèi)型為cf->cmd_type的指令;如果沒(méi)找到打印錯(cuò)誤日志并返回錯(cuò)誤;如果找到還需要校驗(yàn)指令參數(shù)等是否合法;最后才是調(diào)用set函數(shù)設(shè)置。
這些流程都比較簡(jiǎn)單,難點(diǎn)是如何根據(jù)ctx獲取到該配置最終存儲(chǔ)的位置。下面的代碼需要結(jié)合上圖來(lái)分析。配置肯定是存儲(chǔ)在某個(gè)結(jié)構(gòu)體的,所以需要通過(guò)ctx找到對(duì)應(yīng)結(jié)構(gòu)體。
if (cmd->type & NGX_DIRECT_CONF) { //此類(lèi)型的cf->ctx只會(huì)是conf_ctx,直接獲取第index個(gè)元素,說(shuō)明該數(shù)組元素已經(jīng)指向了某個(gè)結(jié)構(gòu)體 conf = ((void **) cf->ctx)[ngx_modules[i]->index]; } else if (cmd->type & NGX_MAIN_CONF) { //此類(lèi)型的cf->ctx只會(huì)是conf_ctx,獲取的是第index個(gè)元素的地址,原因就在于此時(shí)數(shù)組元素指向NULL conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); } else if (cf->ctx) { //此時(shí)cf->ctx可能是events_ctx,http_ctx,srv_ctx或者loc_ctx //假設(shè)cf->ctx為http_ctx,此時(shí)cmd->conf是字段main_conf,srv_conf或者loc_conf在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //一樣是獲取數(shù)組的第ctx_index個(gè)元素,此時(shí)一定是指向某個(gè)結(jié)構(gòu)體 } } rv = cmd->set(cf, cmd, conf); //調(diào)用set函數(shù)設(shè)置,注意這里入?yún)onf1.2 配置文件的解析
函數(shù)ngx_init_cycle會(huì)調(diào)用ngx_conf_parse開(kāi)始配置文件的解析。
解析配置文件首先需要?jiǎng)?chuàng)建配置文件上下文,并初始化結(jié)構(gòu)體ngx_conf_t;
//創(chuàng)建配置文件上下文,并初始化上下文數(shù)組元素 cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));//ngx_max_module為模塊總數(shù)目 //需要遍歷所有核心模塊,并調(diào)用其create_conf創(chuàng)建配置結(jié)構(gòu)體,存儲(chǔ)到上下文數(shù)組 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; } cycle->conf_ctx[ngx_modules[i]->index] = rv; } } //初始化結(jié)構(gòu)體ngx_conf_t conf.ctx = cycle->conf_ctx; conf.module_type = NGX_CORE_MODULE; conf.cmd_type = NGX_MAIN_CONF;
讀者可以查找下代碼,看看哪些核心模塊有create_conf方法。執(zhí)行此步驟之后,可以畫(huà)出下圖:
結(jié)合2.2節(jié)所示的代碼邏輯,可以很容易知道,核心模塊ngx_core_module的配置指令都是帶有NGX_DIRECT_CONF標(biāo)識(shí)的,conf_ctx數(shù)組第0個(gè)元素就指向其配置結(jié)構(gòu)體ngx_core_conf_t。
if (cmd->type & NGX_DIRECT_CONF) { conf = ((void **) cf->ctx)[ngx_modules[i]->index]; } rv = cmd->set(cf, cmd, conf);
以配置worker_processes(設(shè)置worker進(jìn)程數(shù)目)為例,其指令結(jié)構(gòu)定義如下:
{ ngx_string("worker_processes"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_set_worker_processes, 0, 0, NULL }
注意此時(shí)函數(shù)ngx_set_worker_processes入?yún)⒌牡谌齻€(gè)參數(shù)已經(jīng)指向了結(jié)構(gòu)體ngx_core_conf_t,所以可以強(qiáng)制類(lèi)型轉(zhuǎn)換
static char * ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ ngx_core_conf_t *ccf; ccf = (ngx_core_conf_t *) conf; }1.3 events指令塊的解析
ngx_events_module模塊(核心模塊)中定義了events指令結(jié)構(gòu),如下:
{ ngx_string("events"), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_events_block, 0, 0, NULL }
events配置指令處理函數(shù)為ngx_events_block;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cmd->type & NGX_MAIN_CONF) { conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); //此時(shí)cf->ctx仍然是conf_ctx } rv = cmd->set(cf, cmd, conf);
即此時(shí)函數(shù)ngx_events_block的第三個(gè)輸入?yún)?shù)是conf_ctx數(shù)組第index個(gè)元素的地址,且該元素指向NULL。
函數(shù)ngx_events_block主要需要處理3件事:1)創(chuàng)建events_ctx上下文;2)調(diào)用所有事件模塊的create_conf方法創(chuàng)建配置結(jié)構(gòu);3)修改cf->ctx (注意解析events塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析events塊中的配置
static char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { //創(chuàng)建配置上下文events_ctx,只是一個(gè)void*結(jié)構(gòu) ctx = ngx_pcalloc(cf->pool, sizeof(void *)); //數(shù)組,指向所有時(shí)間模塊創(chuàng)建的配置結(jié)構(gòu);ngx_event_max_module為事件模塊數(shù)目 *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *)); //conf是conf_ctx數(shù)組某個(gè)元素的地址;即讓該元素指向配置上下文events_ctx *(void **) conf = ctx; //遍歷所有事件模塊,創(chuàng)建配置結(jié)構(gòu) for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } m = ngx_modules[i]->ctx; if (m->create_conf) { (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle); } } //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量 pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_EVENT_MODULE; cf->cmd_type = NGX_EVENT_CONF; //解析events塊中的配置 rv = ngx_conf_parse(cf, NULL); //還原cf *cf = pcf; }
在linux機(jī)器上采用默認(rèn)選項(xiàng)編譯nginx代碼,事件模塊通常只有ngx_event_core_module和ngx_event_core_module,且兩個(gè)模塊都有create_conf方法,執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖:
以ngx_event_core_module模塊中的配置connections為例(設(shè)置連接池連接數(shù)目),其結(jié)構(gòu)定義如下:
{ ngx_string("connections"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_event_connections, 0, 0, NULL }
connections配置指令處理函數(shù)為ngx_event_connections;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cf->ctx) { //此時(shí)cf->ctx是events_ctx //confp為數(shù)組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數(shù)組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_event_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_event_conf_t
函數(shù)ngx_event_connections實(shí)現(xiàn)較為簡(jiǎn)單,只需要給結(jié)構(gòu)體ngx_event_conf_t相應(yīng)字段賦值即可;注意輸入?yún)?shù)conf指向結(jié)構(gòu)體ngx_event_conf_t,可以直接強(qiáng)制類(lèi)型轉(zhuǎn)換。
static char * ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_event_conf_t *ecf = conf; }1.4 http指令塊的解析
上面學(xué)習(xí)了events指令塊的解析,http指令塊、server指令塊和location指令塊的解析都是非常類(lèi)似的。
ngx_http_module模塊(核心模塊)中定義了http指令結(jié)構(gòu),如下: { ngx_string("http"), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_block, 0, 0, NULL }
http配置指令的處理函數(shù)為ngx_http_block,根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cmd->type & NGX_MAIN_CONF) { conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); //此時(shí)cf->ctx仍然是conf_ctx } rv = cmd->set(cf, cmd, conf);
即此時(shí)函數(shù)ngx_http_block的第三個(gè)輸入?yún)?shù)是conf_ctx數(shù)組第index個(gè)元素的地址,且該元素指向NULL。
函數(shù)ngx_http_block主要需要處理3件事:1)創(chuàng)建http_ctx上下文;2)調(diào)用所有http模塊的create_main_conf、create_srv_conf和create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析http塊中的配置
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ //創(chuàng)建http_ctx配置長(zhǎng)下文 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //conf是conf_ctx數(shù)組某個(gè)元素的地址,即該元素指向http_ctx配置上下文 *(ngx_http_conf_ctx_t **) conf = ctx; //初始化main_conf數(shù)組、srv_conf數(shù)組和loc_conf數(shù)組;ngx_http_max_module為http模塊數(shù)目 ctx->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); //調(diào)用所有http模塊的create_main_conf方法、create_srv_conf方法和create_loc_conf創(chuàng)建相應(yīng)配置結(jié)構(gòu) for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[m]->ctx; mi = ngx_modules[m]->ctx_index; if (module->create_main_conf) { ctx->main_conf[mi] = module->create_main_conf(cf); } if (module->create_srv_conf) { ctx->srv_conf[mi] = module->create_srv_conf(cf); } if (module->create_loc_conf) { ctx->loc_conf[mi] = module->create_loc_conf(cf); } } //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量 pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_HTTP_MODULE; cf->cmd_type = NGX_HTTP_MAIN_CONF; //解析http塊中的配置 rv = ngx_conf_parse(cf, NULL); //還原cf *cf = pcf; }
執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖:
http_ctx配置上下文類(lèi)型為結(jié)構(gòu)體ngx_http_conf_ctx_t,其只有三個(gè)字段main_conf、srv_conf和loc_conf,分別指向一個(gè)數(shù)組,數(shù)組的每個(gè)元素指向的是對(duì)應(yīng)的配置結(jié)構(gòu)。
比如說(shuō)ngx_http_core_module是第一個(gè)http模塊,其create_main_conf方法創(chuàng)建的配置結(jié)構(gòu)為ngx_http_core_main_conf_t。
以ngx_http_core_module模塊的配置keepalive_timeout(該配置可以出現(xiàn)在location塊、server塊和http塊,假設(shè)在http塊中添加該配置)為例,指令結(jié)構(gòu)定義如下:
{ ngx_string("keepalive_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_core_keepalive, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL } #define NGX_HTTP_LOC_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, loc_conf)
可以看到該指令結(jié)構(gòu)的第四個(gè)參數(shù)不為0了,為loc_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量。
keepalive_timeout配置指令處理函數(shù)為ngx_http_core_keepalive;根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cf->ctx) { //此時(shí)cf->ctx是http_ctx //cmd->conf為loc_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;confp為loc_conf數(shù)組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數(shù)組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_loc_conf_t
函數(shù)ngx_http_core_keepalive實(shí)現(xiàn)較為簡(jiǎn)單,這里不做詳述。
1.5 server指令塊的解析ngx_http_core_module模塊中定義了server指令結(jié)構(gòu),如下:
{ ngx_string("server"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_core_server, 0, 0, NULL }
server配置指令的處理函數(shù)為ngx_http_core_server,根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cf->ctx) { //此時(shí)cf->ctx是http_ctx //cmd->conf為0;confp為main_conf數(shù)組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數(shù)組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_main_conf_t
函數(shù)ngx_http_core_server主要需要處理4件事:1)創(chuàng)建srv_ctx上下文;2)調(diào)用所有http模塊的create_srv_conf和create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)將srv_ctx上下文添加到http_ctx配置上下文;4)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析server塊中的配置
static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){ //創(chuàng)建srv_ctx配置上下文 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //cf->ctx為http_ctx配置上下文 http_ctx = cf->ctx; //main_conf共用同一個(gè)(server塊中不會(huì)有NGX_HTTP_MAIN_CONF類(lèi)型的配置,所以其實(shí)是不需要main_conf的) ctx->main_conf = http_ctx->main_conf; ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); //遍歷所有http模塊,調(diào)用其create_srv_conf方法和create_loc_conf創(chuàng)建相應(yīng)配置結(jié)構(gòu) for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); ctx->srv_conf[ngx_modules[i]->ctx_index] = mconf; } if (module->create_loc_conf) { mconf = module->create_loc_conf(cf); ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf; } } //注意這里實(shí)現(xiàn)將srv_ctx上下文添加到http_ctx配置上下文;代碼不好理解,可參考下面的示意圖。 //ngx_http_core_module模塊是第一個(gè)http模塊。獲取其創(chuàng)建的srv_conf類(lèi)型的配置結(jié)構(gòu)ngx_http_core_srv_conf_t;將其ctx字段指向srv_ctx配置上下文 cscf = ctx->srv_conf[ngx_http_core_module.ctx_index]; cscf->ctx = ctx; //main_conf是http_ctx上下文的數(shù)組;獲取其創(chuàng)建的main_conf類(lèi)型的配置結(jié)構(gòu)ngx_http_core_main_conf_t; //并且將,srv_ctx配置上下文的配置結(jié)構(gòu)ngx_http_core_srv_conf_t添加到http_ctx配置上下文的ngx_http_core_main_conf_t配置結(jié)構(gòu)的servers數(shù)組 cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; cscfp = ngx_array_push(&cmcf->servers); *cscfp = cscf; //修改cf的配置上下文,模塊類(lèi)型,指令類(lèi)型;原始cf暫存在pcf變量 pcf = *cf; cf->ctx = ctx; cf->cmd_type = NGX_HTTP_SRV_CONF; //解析server塊中的配置;注意此時(shí)配置上下文為srv_ctx rv = ngx_conf_parse(cf, NULL); //還原cf *cf = pcf; }
執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖,這里只畫(huà)出http_ctx與srv_ctx配置上下文的示意圖:
注意上圖紅色的箭頭,按照紅色箭頭的引用,可以從http_ctx配置上下文找到srv_ctx配置上下文;
看到這里可能會(huì)覺(jué)得存儲(chǔ)結(jié)構(gòu)好復(fù)雜,別著急,等解析location指令塊時(shí),圖還會(huì)更復(fù)雜。
但是不用擔(dān)心,這只是解析時(shí)候的存儲(chǔ)結(jié)構(gòu),最終還會(huì)做一些優(yōu)化,查找時(shí)并不是按照這種結(jié)構(gòu)查找的。
至于server指令塊內(nèi)部的配置,比較簡(jiǎn)單,這里不再舉例詳述。
1.6 location指令塊的解析ngx_http_core_module模塊中定義了location指令結(jié)構(gòu),如下:
{ ngx_string("location"), NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, ngx_http_core_location, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL } #define NGX_HTTP_SRV_CONF_OFFSET offsetof(ngx_http_conf_ctx_t, srv_conf)
可以看到,location指令可以出現(xiàn)在server指令塊和location指令塊(即location本身可以嵌套);location配置可以由一個(gè)或者兩個(gè)參數(shù);注意指令結(jié)構(gòu)第四個(gè)參數(shù)不為0了,為srv_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;指令處理函數(shù)為ngx_http_core_location。根據(jù)其類(lèi)型可以知道在ngx_conf_handler調(diào)用該函數(shù)時(shí)走的是以下分支:
else if (cf->ctx) { //此時(shí)cf->ctx是srv_ctx //cmd->conf為srv_conf字段在結(jié)構(gòu)體ngx_http_conf_ctx_t中的偏移量;confp為srv_conf數(shù)組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數(shù)組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index為0,此時(shí)conf指向結(jié)構(gòu)體ngx_http_core_srv_conf_t
函數(shù)ngx_http_core_server主要需要處理3件事:1)創(chuàng)建loc_ctx上下文;2)調(diào)用所有http模塊的create_loc_conf方法創(chuàng)建配置結(jié)構(gòu);3)將loc_ctx上下文添加到srv_ctx配置上下文;4)修改cf->ctx (注意解析http塊時(shí)配置上下文會(huì)發(fā)生改變),cf->module_type 和cf->cmd_type 并調(diào)用ngx_conf_parse函數(shù)解析location塊中的配置
static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){ //創(chuàng)建loc_conf上下文 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //cf->ctx指向srv_conf上下文 pctx = cf->ctx; //main_conf與srv_conf與srv_ctx上下文公用; //(location塊中不會(huì)有NGX_HTTP_MAIN_CONF和NGX_HTTP_SRV_CONF類(lèi)型的配置,所以其實(shí)是不需要main_conf和srv_conf的) ctx->main_conf = pctx->main_conf; ctx->srv_conf = pctx->srv_conf; ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); //遍歷所有http模塊,調(diào)用其create_loc_conf方法創(chuàng)建相應(yīng)配置結(jié)構(gòu) for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->create_loc_conf) { ctx->loc_conf[ngx_modules[i]->ctx_index] = module->create_loc_conf(cf); } } //ngx_http_core_module是第一個(gè)http模塊;獲取loc_ctx配置上下文的loc_conf數(shù)組的第一個(gè)元素,即ngx_http_core_loc_conf_t結(jié)構(gòu) //將該結(jié)構(gòu)的loc_conf字段指向loc_ctx配置上下文的loc_conf數(shù)組首地址 clcf = ctx->loc_conf[ngx_http_core_module.ctx_index]; clcf->loc_conf = ctx->loc_conf; 獲取srv_ctx配置上下文的loc_conf數(shù)組的第一個(gè)元素,即ngx_http_core_loc_conf_t結(jié)構(gòu) pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index]; //將loc_ctx配置上下文的ngx_http_core_loc_conf_t結(jié)構(gòu)添加到srv_ctx配置上下文的ngx_http_core_loc_conf_t的locations字段 //locations是一個(gè)雙向鏈表,鏈表結(jié)構(gòu)也挺有意思的,有興趣的讀者可以研究下 if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) { } }
執(zhí)行上述代碼之后,可以畫(huà)出以下配置存儲(chǔ)結(jié)構(gòu)圖,這里只畫(huà)出srv_ctx和loc_ctx配置上下文的示意圖:
注意上圖紅色的箭頭,按照紅色箭頭的引用,可以從srv_ctx配置上下文找到loc_ctx配置上下文;其實(shí)這句話是不嚴(yán)謹(jǐn)?shù)模瑴?zhǔn)確的說(shuō),從srv_ctx配置上下文只能找到loc_ctx配置上下文的loc_conf數(shù)組。
原因就在于,所有的配置其實(shí)都是存儲(chǔ)在main_conf數(shù)組、srv_conf數(shù)組和loc_conf數(shù)組。而loc_conf配置上下文的main_conf數(shù)組和srv_conf數(shù)組其實(shí)是沒(méi)有存配置的。
所以只需要loc_conf配置上下文的loc_conf數(shù)組即可。
這里還遺留一個(gè)問(wèn)題,location參數(shù)的解析,這也是我們應(yīng)該關(guān)注的重點(diǎn),將在2.9節(jié)講述。至于location指令塊內(nèi)部的配置,比較簡(jiǎn)單,這里不再舉例詳述。
1.7 配置合并到這一步其實(shí)配置文件已經(jīng)算是解析完成了,但是http相關(guān)存儲(chǔ)結(jié)構(gòu)過(guò)于復(fù)雜。
而且還有一個(gè)問(wèn)題:http_ctx配置上下文和srv_ctx配置上下文都有srv_conf,同時(shí)存儲(chǔ)NGX_HTTP_SRV_CONF類(lèi)型的配置;而http_ctx、srv_ctx和loc_ctx配置上下文都有l(wèi)oc_conf數(shù)組,
同時(shí)存儲(chǔ)NGX_HTTP_LOC_CONF類(lèi)型的配置。那么當(dāng)配置同時(shí)出現(xiàn)在多個(gè)配置上下文中該如何處理,以哪個(gè)為準(zhǔn)呢?
觀察1.1節(jié)nginx模塊的介紹,大多http模塊都有這兩個(gè)方法merge_srv_conf和merge_loc_conf,用于合并不同配置上下文的相同配置。
這里的配置合并其實(shí)就是兩個(gè)srv_conf數(shù)組或者loc_conf數(shù)組的合并。
ngx_http_block函數(shù)中解析完成http塊內(nèi)部所有配置之后,執(zhí)行合并操作。
//此處的ctx是http_ctx配置上下文。不理解的話可以參照上面的示意圖。 cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; cscfp = cmcf->servers.elts; //遍歷所有http模塊(其實(shí)就是遍歷合并srv_conf和loc_conf數(shù)組的每個(gè)元素) for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[m]->ctx; mi = ngx_modules[m]->ctx_index; //init_main_conf是初始化配置默認(rèn)值的,有些配置沒(méi)有賦值時(shí)需要初始化默認(rèn)值 if (module->init_main_conf) { rv = module->init_main_conf(cf, ctx->main_conf[mi]); } //合并 rv = ngx_http_merge_servers(cf, cmcf, module, mi); }
合并操作由函數(shù)ngx_http_merge_servers實(shí)現(xiàn):
static char * ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module, ngx_uint_t ctx_index) { //ngx_http_core_srv_conf_t數(shù)組 cscfp = cmcf->servers.elts; //cf->ctx指向http_ctx配置上下文 ctx = (ngx_http_conf_ctx_t *) cf->ctx; saved = *ctx; //遍歷多個(gè)ngx_http_core_srv_conf_t(多個(gè)server配置) for (s = 0; s < cmcf->servers.nelts; s++) { //通過(guò)ngx_http_core_srv_conf_t可以找到每個(gè)srv_ctx配置上下文的srv_conf數(shù)組 ctx->srv_conf = cscfp[s]->ctx->srv_conf; //合并http_ctx配置上下文的srv_conf數(shù)組中配置到srv_ctx配置上下文的srv_conf數(shù)組 if (module->merge_srv_conf) { rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],cscfp[s]->ctx->srv_conf[ctx_index]); } if (module->merge_loc_conf) { //通過(guò)ngx_http_core_srv_conf_t可以找到每個(gè)srv_ctx配置上下文的loc_conf數(shù)組 ctx->loc_conf = cscfp[s]->ctx->loc_conf; //合并http_ctx配置上下文的loc_conf數(shù)組中配置到srv_ctx配置上下文的loc_conf數(shù)組 rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index], cscfp[s]->ctx->loc_conf[ctx_index]); //合并srv_ctx配置上下文的loc_conf數(shù)組中配置到loc_ctx配置上下文的loc_conf數(shù)組 clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; rv = ngx_http_merge_locations(cf, clcf->locations, cscfp[s]->ctx->loc_conf, module, ctx_index); } } }
函數(shù)ngx_http_merge_locations的實(shí)現(xiàn)與函數(shù)ngx_http_merge_servers基本類(lèi)似,這里不再詳述。合并示意圖如下:
最終http相關(guān)配置存儲(chǔ)在:一個(gè)http_ctx配置上下文的main_conf數(shù)組,多個(gè)srv_ctx配置上下文的srv_conf數(shù)組,多個(gè)loc_ctx配置上下文的loc_conf數(shù)組;為圖中陰影部分。
http_ctx、srv_ctx和loc_ctx之間的引用關(guān)系參考紅色箭頭。
問(wèn)題就在于如何查找到多個(gè)srv_ctx配置上下文的srv_conf數(shù)組,多個(gè)loc_ctx配置上下文的loc_conf數(shù)組,將在第3節(jié)介紹。
1.8 location配置優(yōu)化location配置的語(yǔ)法規(guī)則是:location [=|~|~*|^~] /uri/ { … },可以簡(jiǎn)單講location配置分為三種類(lèi)型:精確匹配,最大前綴匹配和正則匹配。
分類(lèi)規(guī)則如下:1)以“=”開(kāi)始的為精確匹配;2)以“~”和“~*”開(kāi)始的分別為為區(qū)分大小寫(xiě)的正則匹配和不區(qū)分大小寫(xiě)的正則匹配;3)以“^~”開(kāi)始的是最大前綴匹配;4)參數(shù)只有/uri的是最大前綴匹配。
可以看到類(lèi)型3和類(lèi)型4都是最大類(lèi)型匹配,那么這兩者有什么區(qū)別呢?在查找匹配location時(shí)可以看到。
那么當(dāng)我們配置了多個(gè)locaiton,且請(qǐng)求uri可以滿足多個(gè)location的匹配規(guī)則時(shí),最終選擇哪個(gè)配置呢?不同location類(lèi)型有不同的匹配優(yōu)先級(jí)。
我們先看下location配置的分類(lèi),顯然可以根據(jù)第一個(gè)字符來(lái)分類(lèi),location配置的參數(shù)以及類(lèi)型等信息都存儲(chǔ)在ngx_http_core_loc_conf_t以下幾個(gè)字段:
struct ngx_http_core_loc_conf_s { ngx_str_t name; //名稱(chēng),即location配置的uri參數(shù) ngx_http_regex_t *regex; //編譯后的正則表達(dá)式,可標(biāo)識(shí)類(lèi)型2 unsigned exact_match:1; //標(biāo)識(shí)以=開(kāi)頭的location配置,類(lèi)型1 unsigned noregex:1; //查找匹配的location配置時(shí)有用。標(biāo)識(shí)匹配到該location之后,不再?lài)L試匹配正則類(lèi)型的locaiton;類(lèi)型3帶有此標(biāo)識(shí) ngx_http_location_tree_node_t *static_locations; //通過(guò)命名可以看到這是一棵樹(shù)(存儲(chǔ)的是類(lèi)型為1,3和4的locaiton配置) ngx_http_core_loc_conf_t **regex_locations; //存儲(chǔ)所有的正則匹配 }
2.7節(jié)解析location指令塊時(shí)提到,srv_ctx上下文的loc_conf數(shù)組,第一個(gè)元素指向類(lèi)型為ngx_http_core_loc_conf_t的結(jié)構(gòu)體,結(jié)構(gòu)體的locations字段時(shí)一個(gè)雙向鏈表,存儲(chǔ)的是當(dāng)前server指令塊內(nèi)部配置的所有l(wèi)ocation。
雙向鏈表節(jié)點(diǎn)定義如下:
typedef struct { ngx_queue_t queue; //雙向鏈表統(tǒng)一頭部;該結(jié)構(gòu)維護(hù)了prev和next指針; ngx_http_core_loc_conf_t *exact; //類(lèi)型為1和2的location配置存儲(chǔ)在鏈表節(jié)點(diǎn)的此字段 ngx_http_core_loc_conf_t *inclusive; //類(lèi)型為3和4的location配置存儲(chǔ)在鏈表節(jié)點(diǎn)的此字段 } ngx_http_location_queue_t;
location已經(jīng)按照類(lèi)型做好了標(biāo)記,且存儲(chǔ)在雙向鏈表,為了實(shí)現(xiàn)location的高效優(yōu)先級(jí)查找,需要給location配置排序,同時(shí)將多個(gè)location配置形成一棵樹(shù)。
這些操作都是由函數(shù)ngx_http_block 執(zhí)行的,且在解析http塊內(nèi)的所有配置之后。
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ //指向比較亂。需要參考上面示意圖的紅色箭頭。 //ctx指向http_ctx配置上下文 cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; cscfp = cmcf->servers.elts; //遍歷所有srv_ctx上下文 for (s = 0; s < cmcf->servers.nelts; s++) { clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; //該方法實(shí)現(xiàn)了location配置排序,以及將雙向鏈表中正則類(lèi)型的location配置裁剪出來(lái) if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) { return NGX_CONF_ERROR; } //雙向鏈表中只剩下類(lèi)型1、3和4的location配置了 if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) { return NGX_CONF_ERROR; } } }
下面分別分析location配置排序,正則類(lèi)型location配置的裁剪,以及形成location樹(shù):
1)location配置排序由函數(shù)ngx_queue_sort(ngx_queue_t queue, ngx_int_t (cmp)(const ngx_queue_t , const ngx_queue_t ))實(shí)現(xiàn),輸入?yún)?shù)queue為雙向鏈表,cmp為鏈表節(jié)點(diǎn)的比較函數(shù)。ngx_queue_sort函數(shù)按照從小到大排序,且采用穩(wěn)定的排序算法(兩個(gè)元素相等時(shí),排序后順序與排序之前的原始順序相同)
locations雙向鏈表節(jié)點(diǎn)的比較函數(shù)為ngx_http_cmp_locations,通過(guò)該函數(shù)就可以知道location配置的排序規(guī)則,實(shí)現(xiàn)如下:
//與一般比較函數(shù)一樣,返回1表示one大于two;0表示兩者相等;-1表示one小于two static ngx_int_t ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two) { //正則類(lèi)型的配置大于其余類(lèi)型的配置 if (first->regex && !second->regex) { return 1; } if (!first->regex && second->regex) { return -1; } if (first->regex || second->regex) { return 0; } rc = ngx_filename_cmp(first->name.data, second->name.data, ngx_min(first->name.len, second->name.len) + 1); //按照l(shuí)ocation名稱(chēng),即uri排序;且當(dāng)兩個(gè)uri前綴相同時(shí),保證精確匹配類(lèi)型的location排在前面 if (rc == 0 && !first->exact_match && second->exact_match) { return 1; } return rc; }
按照上述比較函數(shù)的規(guī)則排序后,正則類(lèi)型的location配置一定是排列在雙向鏈表尾部;精確匹配和最大前綴匹配首先按照uri字母序排列,且當(dāng)兩個(gè)uri前綴相同時(shí),精確匹配類(lèi)型排列在最大前綴匹配的前面。
2)經(jīng)歷了第一步location已經(jīng)排好序了,且正則類(lèi)型的排列在雙向鏈表尾部,這樣就很容易裁剪出所有正則類(lèi)型的location配置了。只需要從頭到尾遍歷雙向鏈表,直至查找到正則類(lèi)型的location配置,并從該位置出將雙向鏈表拆分開(kāi)來(lái)即可。
3)雙向鏈表中只剩下精確匹配類(lèi)型和最大前綴匹配類(lèi)型的location配置了,且都是按照uri字母序排序的,這些配置會(huì)被組織成一棵樹(shù),方便查找。
形成的這棵樹(shù)是一棵三叉樹(shù),每個(gè)節(jié)點(diǎn)node都有三個(gè)子節(jié)點(diǎn),left、tree和right。left一定小于node;right一定大于node;tree與node前綴相同,且tree節(jié)點(diǎn)uri長(zhǎng)度一定大于node節(jié)點(diǎn)uri長(zhǎng)度。
注意只有最大前綴匹配的配置才有tree節(jié)點(diǎn)。
思考下為什么會(huì)有tree節(jié)點(diǎn),且最大前綴匹配才有tree節(jié)點(diǎn)呢?node匹配成功后,tree節(jié)點(diǎn)還有可能匹配成功。
形成樹(shù)過(guò)程這里不做詳述,有興趣的讀者可以研究下函數(shù)ngx_http_init_static_location_trees的實(shí)現(xiàn)。
總結(jié)至此配置文件解析完成,http、server和location相關(guān)配置最終存儲(chǔ)在main_conf、多個(gè)srv_conf和多個(gè)loc_conf數(shù)組中,但是當(dāng)服務(wù)器接收到客戶端請(qǐng)求時(shí),如何查找對(duì)應(yīng)的srv_conf數(shù)組和loc_conf數(shù)組呢?
將在第三篇《nginx配置文件解析(三)》講解。
希望交流,一起學(xué)習(xí)Nginx PHP Redis 等源碼的朋友請(qǐng)入微信群:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/40171.html
摘要:本文將從源碼從此深入分析配置文件的解析,配置存儲(chǔ),與配置查找。在學(xué)習(xí)配置文件的解析過(guò)程之前,需要先了解一下模塊與指令的一些基本知識(shí)。 運(yùn)營(yíng)研發(fā)團(tuán)隊(duì) 李樂(lè) 配置文件是nginx的基礎(chǔ),對(duì)于學(xué)習(xí)nginx源碼甚至開(kāi)發(fā)nginx模塊的同學(xué)來(lái)說(shuō)更是必須深究。本文將從源碼從此深入分析nginx配置文件的解析,配置存儲(chǔ),與配置查找。 看本文之前讀者可以先思考兩個(gè)問(wèn)題: 1.nginx源碼中隨處可以...
摘要:四監(jiān)聽(tīng)套接字的使用假設(shè)此處我們使用作為事件處理模塊在增加事件時(shí)用戶可以使用中的字段當(dāng)事件發(fā)生時(shí)該字段也會(huì)帶回。在創(chuàng)建監(jiān)聽(tīng)套接字時(shí)將結(jié)構(gòu)分為級(jí)監(jiān)聽(tīng)套接字地址各級(jí)都是一對(duì)多的關(guān)系。 施洪寶 一. 基礎(chǔ) nginx源碼采用1.15.5 后續(xù)部分僅討論http中的listen配置解析以及優(yōu)化流程 1.1 概述 假設(shè)nginx http模塊的配置如下 http{ server { ...
摘要:每個(gè)模塊由以下幾部分構(gòu)成結(jié)構(gòu)體代表模塊本身,其指針被放入數(shù)組中。結(jié)構(gòu)體用來(lái)表示模塊的配置內(nèi)容,其中部分成員可以通過(guò)配置文件進(jìn)行配置。調(diào)用該中的函數(shù),該函數(shù)最終初始化模塊對(duì)應(yīng)的結(jié)構(gòu)體,完成配置。因此,分析源碼中的配置指令,就是分析結(jié)構(gòu)體。 本篇的上篇 Nginx 源碼分析:從模塊到配置(上),建議閱讀本篇前先閱讀上篇。 關(guān)于模塊 Nginx的架構(gòu)高度模塊化。每個(gè)模塊各司其職,組合在一...
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
摘要:表示的是兩個(gè),當(dāng)其中任意一個(gè)計(jì)算完并發(fā)編程之是線程安全并且高效的,在并發(fā)編程中經(jīng)??梢?jiàn)它的使用,在開(kāi)始分析它的高并發(fā)實(shí)現(xiàn)機(jī)制前,先講講廢話,看看它是如何被引入的。電商秒殺和搶購(gòu),是兩個(gè)比較典型的互聯(lián)網(wǎng)高并發(fā)場(chǎng)景。 干貨:深度剖析分布式搜索引擎設(shè)計(jì) 分布式,高可用,和機(jī)器學(xué)習(xí)一樣,最近幾年被提及得最多的名詞,聽(tīng)名字多牛逼,來(lái),我們一步一步來(lái)?yè)羝魄皟蓚€(gè)名詞,今天我們首先來(lái)說(shuō)說(shuō)分布式。 探究...
閱讀 2322·2021-11-24 09:39
閱讀 3055·2021-10-15 09:39
閱讀 3106·2021-07-26 23:38
閱讀 2301·2019-08-30 11:14
閱讀 3420·2019-08-29 16:39
閱讀 1723·2019-08-29 15:23
閱讀 791·2019-08-29 13:01
閱讀 2673·2019-08-29 12:29