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