【Nginx源碼分析】Nginx配置文件解析(二)

運營研發團隊 李樂 node

本文做爲nginx配置文件解析的第二篇,開始講解nginx配置文件解析的源碼,在閱讀本文以前,但願你已經閱讀過第一篇。《nginx配置文件解析(一)》linux

1.1配置解析流程

解析配置的入口函數是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,暫時無論
};

重點須要關注這些字段:算法

  • 1)name和args存儲當前讀取到的指令信息;
  • 2)ctx上下文,就是咱們上面所說的指令上下文,想象下若是沒有ctx咱們獲取該指令最終存儲的位置;
  • 3)module_type和cmd_type分別表示模塊類型與指令類型;讀取到某條指令時,須要遍歷全部模塊的指令數組,經過這兩個字段能夠過濾某些不該該解析該配置的模塊與指令。

函數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

1.2 配置文件的解析

函數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方法。執行此步驟以後,能夠畫出下圖:

clipboard.png

結合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;
}

1.3 events指令塊的解析

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方法,執行上述代碼以後,能夠畫出如下配置存儲結構圖:

clipboard.png

以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;
}

1.4 http指令塊的解析

上面學習了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;
}

執行上述代碼以後,能夠畫出如下配置存儲結構圖:

clipboard.png

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實現較爲簡單,這裏不作詳述。

1.5 server指令塊的解析

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配置上下文的示意圖:

clipboard.png

注意上圖紅色的箭頭,按照紅色箭頭的引用,能夠從http_ctx配置上下文找到srv_ctx配置上下文;

看到這裏可能會以爲存儲結構好複雜,彆着急,等解析location指令塊時,圖還會更復雜。

可是不用擔憂,這只是解析時候的存儲結構,最終還會作一些優化,查找時並非按照這種結構查找的。

至於server指令塊內部的配置,比較簡單,這裏再也不舉例詳述。

1.6 location指令塊的解析

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配置上下文的示意圖:

clipboard.png

注意上圖紅色的箭頭,按照紅色箭頭的引用,能夠從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指令塊內部的配置,比較簡單,這裏再也不舉例詳述。

1.7 配置合併

到這一步其實配置文件已經算是解析完成了,可是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基本相似,這裏再也不詳述。合併示意圖以下:

clipboard.png

最終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節介紹。

1.8 location配置優化

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樹:

  • 1)location配置排序由函數ngx_queue_sort(ngx_queue_t queue, ngx_int_t (cmp)(const ngx_queue_t , const ngx_queue_t ))實現,輸入參數queue爲雙向鏈表,cmp爲鏈表節點的比較函數。ngx_queue_sort函數按照從小到大排序,且採用穩定的排序算法(兩個元素相等時,排序後順序與排序以前的原始順序相同)

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前綴相同時,精確匹配類型排列在最大前綴匹配的前面。

  • 2)經歷了第一步location已經排好序了,且正則類型的排列在雙向鏈表尾部,這樣就很容易裁剪出全部正則類型的location配置了。只須要從頭至尾遍歷雙向鏈表,直至查找到正則類型的location配置,並從該位置出將雙向鏈表拆分開來便可。
  • 3)雙向鏈表中只剩下精確匹配類型和最大前綴匹配類型的location配置了,且都是按照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 等源碼的朋友請入微信羣:

clipboard.png

相關文章
相關標籤/搜索