運營研發團隊 李樂 node
本文做爲nginx配置文件解析的第二篇,開始講解nginx配置文件解析的源碼,在閱讀本文以前,但願你已經閱讀過第一篇。《nginx配置文件解析(一)》linux
解析配置的入口函數是ngx_conf_parse(ngx_conf_t cf, ngx_str_t filename),其輸入參數filename表示配置文件路徑,若是爲NULL代表此時解析的是指令塊。nginx
那麼cf是什麼呢?先看看其結構體聲明:正則表達式
struct ngx_conf_s { char *name; //當前讀取到的指令名稱 ngx_array_t *args; //當前讀取到的指令參數 ngx_cycle_t *cycle; //指向全局cycle ngx_pool_t *pool; //內存池 ngx_conf_file_t *conf_file; //配置文件 void *ctx; //上下文 ngx_uint_t module_type; //模塊類型 ngx_uint_t cmd_type; //指令類型 ngx_conf_handler_pt handler; //通常都是NULL,暫時無論 };
重點須要關注這些字段:算法
函數ngx_conf_parse邏輯比較簡單,就是讀取完整指令,並調用函數ngx_conf_handler處理指令。segmentfault
函數ngx_conf_handler主要邏輯是,遍歷類型爲cf->module_type的模塊,查找該模塊指令數組中類型爲cf->cmd_type的指令;若是沒找到打印錯誤日誌並返回錯誤;若是找到還須要校驗指令參數等是否合法;最後纔是調用set函數設置。數組
這些流程都比較簡單,難點是如何根據ctx獲取到該配置最終存儲的位置。下面的代碼須要結合上圖來分析。配置確定是存儲在某個結構體的,因此須要經過ctx找到對應結構體。服務器
if (cmd->type & NGX_DIRECT_CONF) { //此類型的cf->ctx只會是conf_ctx,直接獲取第index個元素,說明該數組元素已經指向了某個結構體 conf = ((void **) cf->ctx)[ngx_modules[i]->index]; } else if (cmd->type & NGX_MAIN_CONF) { //此類型的cf->ctx只會是conf_ctx,獲取的是第index個元素的地址,緣由就在於此時數組元素指向NULL conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); } else if (cf->ctx) { //此時cf->ctx多是events_ctx,http_ctx,srv_ctx或者loc_ctx //假設cf->ctx爲http_ctx,此時cmd->conf是字段main_conf,srv_conf或者loc_conf在結構體ngx_http_conf_ctx_t中的偏移量 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //同樣是獲取數組的第ctx_index個元素,此時必定是指向某個結構體 } } rv = cmd->set(cf, cmd, conf); //調用set函數設置,注意這裏入參conf
函數ngx_init_cycle會調用ngx_conf_parse開始配置文件的解析。微信
解析配置文件首先須要建立配置文件上下文,並初始化結構體ngx_conf_t;函數
//建立配置文件上下文,並初始化上下文數組元素 cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));//ngx_max_module爲模塊總數目 //須要遍歷全部核心模塊,並調用其create_conf建立配置結構體,存儲到上下文數組 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; } } //初始化結構體ngx_conf_t conf.ctx = cycle->conf_ctx; conf.module_type = NGX_CORE_MODULE; conf.cmd_type = NGX_MAIN_CONF;
讀者能夠查找下代碼,看看哪些核心模塊有create_conf方法。執行此步驟以後,能夠畫出下圖:
結合2.2節所示的代碼邏輯,能夠很容易知道,核心模塊ngx_core_module的配置指令都是帶有NGX_DIRECT_CONF標識的,conf_ctx數組第0個元素就指向其配置結構體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(設置worker進程數目)爲例,其指令結構定義以下:
{ ngx_string("worker_processes"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, ngx_set_worker_processes, 0, 0, NULL }
注意此時函數ngx_set_worker_processes入參的第三個參數已經指向告終構體ngx_core_conf_t,因此能夠強制類型轉換
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; }
ngx_events_module模塊(核心模塊)中定義了events指令結構,以下:
{ ngx_string("events"), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_events_block, 0, 0, NULL }
events配置指令處理函數爲ngx_events_block;根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
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);
即此時函數ngx_events_block的第三個輸入參數是conf_ctx數組第index個元素的地址,且該元素指向NULL。
函數ngx_events_block主要須要處理3件事:1)建立events_ctx上下文;2)調用全部事件模塊的create_conf方法建立配置結構;3)修改cf->ctx (注意解析events塊時配置上下文會發生改變),cf->module_type 和cf->cmd_type 並調用ngx_conf_parse函數解析events塊中的配置
static char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { //建立配置上下文events_ctx,只是一個void*結構 ctx = ngx_pcalloc(cf->pool, sizeof(void *)); //數組,指向全部時間模塊建立的配置結構;ngx_event_max_module爲事件模塊數目 *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *)); //conf是conf_ctx數組某個元素的地址;即讓該元素指向配置上下文events_ctx *(void **) conf = ctx; //遍歷全部事件模塊,建立配置結構 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方法,執行上述代碼以後,能夠畫出如下配置存儲結構圖:
以ngx_event_core_module模塊中的配置connections爲例(設置鏈接池鏈接數目),其結構定義以下:
{ ngx_string("connections"), NGX_EVENT_CONF|NGX_CONF_TAKE1, ngx_event_connections, 0, 0, NULL }
connections配置指令處理函數爲ngx_event_connections;根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
else if (cf->ctx) { //此時cf->ctx是events_ctx //confp爲數組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_event_core_module的ctx_index爲0,此時conf指向結構體ngx_event_conf_t
函數ngx_event_connections實現較爲簡單,只須要給結構體ngx_event_conf_t相應字段賦值便可;注意輸入參數conf指向結構體ngx_event_conf_t,能夠直接強制類型轉換。
static char * ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_event_conf_t *ecf = conf; }
上面學習了events指令塊的解析,http指令塊、server指令塊和location指令塊的解析都是很是相似的。
ngx_http_module模塊(核心模塊)中定義了http指令結構,以下: { ngx_string("http"), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_block, 0, 0, NULL }
http配置指令的處理函數爲ngx_http_block,根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
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);
即此時函數ngx_http_block的第三個輸入參數是conf_ctx數組第index個元素的地址,且該元素指向NULL。
函數ngx_http_block主要須要處理3件事:1)建立http_ctx上下文;2)調用全部http模塊的create_main_conf、create_srv_conf和create_loc_conf方法建立配置結構;3)修改cf->ctx (注意解析http塊時配置上下文會發生改變),cf->module_type 和cf->cmd_type 並調用ngx_conf_parse函數解析http塊中的配置
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ //建立http_ctx配置長下文 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //conf是conf_ctx數組某個元素的地址,即該元素指向http_ctx配置上下文 *(ngx_http_conf_ctx_t **) conf = ctx; //初始化main_conf數組、srv_conf數組和loc_conf數組;ngx_http_max_module爲http模塊數目 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); //調用全部http模塊的create_main_conf方法、create_srv_conf方法和create_loc_conf建立相應配置結構 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; }
執行上述代碼以後,能夠畫出如下配置存儲結構圖:
http_ctx配置上下文類型爲結構體ngx_http_conf_ctx_t,其只有三個字段main_conf、srv_conf和loc_conf,分別指向一個數組,數組的每一個元素指向的是對應的配置結構。
好比說ngx_http_core_module是第一個http模塊,其create_main_conf方法建立的配置結構爲ngx_http_core_main_conf_t。
以ngx_http_core_module模塊的配置keepalive_timeout(該配置能夠出如今location塊、server塊和http塊,假設在http塊中添加該配置)爲例,指令結構定義以下:
{ 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)
能夠看到該指令結構的第四個參數不爲0了,爲loc_conf字段在結構體ngx_http_conf_ctx_t中的偏移量。
keepalive_timeout配置指令處理函數爲ngx_http_core_keepalive;根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
else if (cf->ctx) { //此時cf->ctx是http_ctx //cmd->conf爲loc_conf字段在結構體ngx_http_conf_ctx_t中的偏移量;confp爲loc_conf數組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index爲0,此時conf指向結構體ngx_http_core_loc_conf_t
函數ngx_http_core_keepalive實現較爲簡單,這裏不作詳述。
ngx_http_core_module模塊中定義了server指令結構,以下:
{ ngx_string("server"), NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_core_server, 0, 0, NULL }
server配置指令的處理函數爲ngx_http_core_server,根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
else if (cf->ctx) { //此時cf->ctx是http_ctx //cmd->conf爲0;confp爲main_conf數組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index爲0,此時conf指向結構體ngx_http_core_main_conf_t
函數ngx_http_core_server主要須要處理4件事:1)建立srv_ctx上下文;2)調用全部http模塊的create_srv_conf和create_loc_conf方法建立配置結構;3)將srv_ctx上下文添加到http_ctx配置上下文;4)修改cf->ctx (注意解析http塊時配置上下文會發生改變),cf->module_type 和cf->cmd_type 並調用ngx_conf_parse函數解析server塊中的配置
static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){ //建立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模塊,調用其create_srv_conf方法和create_loc_conf建立相應配置結構 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; } } //注意這裏實現將srv_ctx上下文添加到http_ctx配置上下文;代碼很差理解,可參考下面的示意圖。 //ngx_http_core_module模塊是第一個http模塊。獲取其建立的srv_conf類型的配置結構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上下文的數組;獲取其建立的main_conf類型的配置結構ngx_http_core_main_conf_t; //而且將,srv_ctx配置上下文的配置結構ngx_http_core_srv_conf_t添加到http_ctx配置上下文的ngx_http_core_main_conf_t配置結構的servers數組 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; }
執行上述代碼以後,能夠畫出如下配置存儲結構圖,這裏只畫出http_ctx與srv_ctx配置上下文的示意圖:
注意上圖紅色的箭頭,按照紅色箭頭的引用,能夠從http_ctx配置上下文找到srv_ctx配置上下文;
看到這裏可能會以爲存儲結構好複雜,彆着急,等解析location指令塊時,圖還會更復雜。
可是不用擔憂,這只是解析時候的存儲結構,最終還會作一些優化,查找時並非按照這種結構查找的。
至於server指令塊內部的配置,比較簡單,這裏再也不舉例詳述。
ngx_http_core_module模塊中定義了location指令結構,以下:
{ 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指令能夠出如今server指令塊和location指令塊(即location自己能夠嵌套);location配置能夠由一個或者兩個參數;注意指令結構第四個參數不爲0了,爲srv_conf字段在結構體ngx_http_conf_ctx_t中的偏移量;指令處理函數爲ngx_http_core_location。根據其類型能夠知道在ngx_conf_handler調用該函數時走的是如下分支:
else if (cf->ctx) { //此時cf->ctx是srv_ctx //cmd->conf爲srv_conf字段在結構體ngx_http_conf_ctx_t中的偏移量;confp爲srv_conf數組首地址 confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[ngx_modules[i]->ctx_index]; //獲取數組元素 } } rv = cmd->set(cf, cmd, conf); //ngx_http_core_module的ctx_index爲0,此時conf指向結構體ngx_http_core_srv_conf_t
函數ngx_http_core_server主要須要處理3件事:1)建立loc_ctx上下文;2)調用全部http模塊的create_loc_conf方法建立配置結構;3)將loc_ctx上下文添加到srv_ctx配置上下文;4)修改cf->ctx (注意解析http塊時配置上下文會發生改變),cf->module_type 和cf->cmd_type 並調用ngx_conf_parse函數解析location塊中的配置
static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy){ //建立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模塊,調用其create_loc_conf方法建立相應配置結構 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數組的第一個元素,即ngx_http_core_loc_conf_t結構 //將該結構的loc_conf字段指向loc_ctx配置上下文的loc_conf數組首地址 clcf = ctx->loc_conf[ngx_http_core_module.ctx_index]; clcf->loc_conf = ctx->loc_conf; 獲取srv_ctx配置上下文的loc_conf數組的第一個元素,即ngx_http_core_loc_conf_t結構 pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index]; //將loc_ctx配置上下文的ngx_http_core_loc_conf_t結構添加到srv_ctx配置上下文的ngx_http_core_loc_conf_t的locations字段 //locations是一個雙向鏈表,鏈表結構也挺有意思的,有興趣的讀者能夠研究下 if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) { } }
執行上述代碼以後,能夠畫出如下配置存儲結構圖,這裏只畫出srv_ctx和loc_ctx配置上下文的示意圖:
注意上圖紅色的箭頭,按照紅色箭頭的引用,能夠從srv_ctx配置上下文找到loc_ctx配置上下文;其實這句話是不嚴謹的,準確的說,從srv_ctx配置上下文只能找到loc_ctx配置上下文的loc_conf數組。
緣由就在於,全部的配置其實都是存儲在main_conf數組、srv_conf數組和loc_conf數組。而loc_conf配置上下文的main_conf數組和srv_conf數組實際上是沒有存配置的。
因此只須要loc_conf配置上下文的loc_conf數組便可。
這裏還遺留一個問題,location參數的解析,這也是咱們應該關注的重點,將在2.9節講述。至於location指令塊內部的配置,比較簡單,這裏再也不舉例詳述。
到這一步其實配置文件已經算是解析完成了,可是http相關存儲結構過於複雜。
並且還有一個問題:http_ctx配置上下文和srv_ctx配置上下文都有srv_conf,同時存儲NGX_HTTP_SRV_CONF類型的配置;而http_ctx、srv_ctx和loc_ctx配置上下文都有loc_conf數組,
同時存儲NGX_HTTP_LOC_CONF類型的配置。那麼當配置同時出如今多個配置上下文中該如何處理,以哪一個爲準呢?
觀察1.1節nginx模塊的介紹,大多http模塊都有這兩個方法merge_srv_conf和merge_loc_conf,用於合併不一樣配置上下文的相同配置。
這裏的配置合併其實就是兩個srv_conf數組或者loc_conf數組的合併。
ngx_http_block函數中解析完成http塊內部全部配置以後,執行合併操做。
//此處的ctx是http_ctx配置上下文。不理解的話能夠參照上面的示意圖。 cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; cscfp = cmcf->servers.elts; //遍歷全部http模塊(其實就是遍歷合併srv_conf和loc_conf數組的每一個元素) 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); }
合併操做由函數ngx_http_merge_servers實現:
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數組 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數組 ctx->srv_conf = cscfp[s]->ctx->srv_conf; //合併http_ctx配置上下文的srv_conf數組中配置到srv_ctx配置上下文的srv_conf數組 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數組 ctx->loc_conf = cscfp[s]->ctx->loc_conf; //合併http_ctx配置上下文的loc_conf數組中配置到srv_ctx配置上下文的loc_conf數組 rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index], cscfp[s]->ctx->loc_conf[ctx_index]); //合併srv_ctx配置上下文的loc_conf數組中配置到loc_ctx配置上下文的loc_conf數組 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); } } }
函數ngx_http_merge_locations的實現與函數ngx_http_merge_servers基本相似,這裏再也不詳述。合併示意圖以下:
最終http相關配置存儲在:一個http_ctx配置上下文的main_conf數組,多個srv_ctx配置上下文的srv_conf數組,多個loc_ctx配置上下文的loc_conf數組;爲圖中陰影部分。
http_ctx、srv_ctx和loc_ctx之間的引用關係參考紅色箭頭。
問題就在於如何查找到多個srv_ctx配置上下文的srv_conf數組,多個loc_ctx配置上下文的loc_conf數組,將在第3節介紹。
location配置的語法規則是:location [=|~|~*|^~] /uri/ { … },能夠簡單講location配置分爲三種類型:精確匹配,最大前綴匹配和正則匹配。
分類規則以下:1)以「=」開始的爲精確匹配;2)以「~」和「~*」開始的分別爲爲區分大小寫的正則匹配和不區分大小寫的正則匹配;3)以「^~」開始的是最大前綴匹配;4)參數只有/uri的是最大前綴匹配。
能夠看到類型3和類型4都是最大類型匹配,那麼這二者有什麼區別呢?在查找匹配location時能夠看到。
那麼當咱們配置了多個locaiton,且請求uri能夠知足多個location的匹配規則時,最終選擇哪一個配置呢?不一樣location類型有不一樣的匹配優先級。
咱們先看下location配置的分類,顯然能夠根據第一個字符來分類,location配置的參數以及類型等信息都存儲在ngx_http_core_loc_conf_t如下幾個字段:
struct ngx_http_core_loc_conf_s { ngx_str_t name; //名稱,即location配置的uri參數 ngx_http_regex_t *regex; //編譯後的正則表達式,可標識類型2 unsigned exact_match:1; //標識以=開頭的location配置,類型1 unsigned noregex:1; //查找匹配的location配置時有用。標識匹配到該location以後,再也不嘗試匹配正則類型的locaiton;類型3帶有此標識 ngx_http_location_tree_node_t *static_locations; //經過命名能夠看到這是一棵樹(存儲的是類型爲1,3和4的locaiton配置) ngx_http_core_loc_conf_t **regex_locations; //存儲全部的正則匹配 }
2.7節解析location指令塊時提到,srv_ctx上下文的loc_conf數組,第一個元素指向類型爲ngx_http_core_loc_conf_t的結構體,結構體的locations字段時一個雙向鏈表,存儲的是當前server指令塊內部配置的全部location。
雙向鏈表節點定義以下:
typedef struct { ngx_queue_t queue; //雙向鏈表統一頭部;該結構維護了prev和next指針; ngx_http_core_loc_conf_t *exact; //類型爲1和2的location配置存儲在鏈表節點的此字段 ngx_http_core_loc_conf_t *inclusive; //類型爲3和4的location配置存儲在鏈表節點的此字段 } ngx_http_location_queue_t;
location已經按照類型作好了標記,且存儲在雙向鏈表,爲了實現location的高效優先級查找,須要給location配置排序,同時將多個location配置造成一棵樹。
這些操做都是由函數ngx_http_block 執行的,且在解析http塊內的全部配置以後。
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]; //該方法實現了location配置排序,以及將雙向鏈表中正則類型的location配置裁剪出來 if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) { return NGX_CONF_ERROR; } //雙向鏈表中只剩下類型一、3和4的location配置了 if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) { return NGX_CONF_ERROR; } } }
下面分別分析location配置排序,正則類型location配置的裁剪,以及造成location樹:
locations雙向鏈表節點的比較函數爲ngx_http_cmp_locations,經過該函數就能夠知道location配置的排序規則,實現以下:
//與通常比較函數同樣,返回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排序;且當兩個uri前綴相同時,保證精確匹配類型的location排在前面 if (rc == 0 && !first->exact_match && second->exact_match) { return 1; } return rc; }
按照上述比較函數的規則排序後,正則類型的location配置必定是排列在雙向鏈表尾部;精確匹配和最大前綴匹配首先按照uri字母序排列,且當兩個uri前綴相同時,精確匹配類型排列在最大前綴匹配的前面。
造成的這棵樹是一棵三叉樹,每一個節點node都有三個子節點,left、tree和right。left必定小於node;right必定大於node;tree與node前綴相同,且tree節點uri長度必定大於node節點uri長度。
注意只有最大前綴匹配的配置纔有tree節點。
思考下爲何會有tree節點,且最大前綴匹配纔有tree節點呢?node匹配成功後,tree節點還有可能匹配成功。
造成樹過程這裏不作詳述,有興趣的讀者能夠研究下函數ngx_http_init_static_location_trees的實現。
至此配置文件解析完成,http、server和location相關配置最終存儲在main_conf、多個srv_conf和多個loc_conf數組中,可是當服務器接收到客戶端請求時,如何查找對應的srv_conf數組和loc_conf數組呢?
將在第三篇《nginx配置文件解析(三)》講解。
但願交流,一塊兒學習Nginx PHP Redis 等源碼的朋友請入微信羣: