合併http配置項

  • 微信公衆號:鄭爾多斯
  • 關注「鄭爾多斯」公衆號 ,回覆「領取資源」,獲取IT資源500G乾貨。
    升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峯!想一想還有點小激動
  • 關注可瞭解更多的Nginx知識。任何問題或建議,請公衆號留言;
    關注公衆號,有趣有內涵的文章第一時間送達!

前言

前面的文章已經從源碼級別將配置的解析過程說的很清楚了,本文咱們學習一下nginxhttp配置的merge過程。咱們知道nginxhttp配置結構體是一個很是複雜的東東,一樣的指令可能出如今不一樣的位置,好比root指令,既能夠出如今httpmain級別,也能夠出如今serverlocationif 等上下文中,那麼當一個請求到來的時候,nginx會使用哪個上下文中的配置結果呢?這就牽涉到了http配置的merge過程。nginx

背景

首先咱們瞭解下merge 的背景:數組

  • 所謂merge 操做,就是合併內外層的配置。大致原則是:若是內層沒有配置,那麼之外層爲準,若是都沒有配置,那麼就用默認值;
  • NGX_CORE_MODULE 模塊的ctx(ngx_core_module_t)是沒有 merge 操做的,因此像 http 塊這一層的配置是不須要和上一層去merge 的,想一想也明白爲何,http哪來的上一層呢?
  • NGX_HTTP_MODULE模塊的 ctx(ngx_http_module_t)是有 merge 操做的,可是僅僅有 merge_srv_confmerge_loc_conf,同理對 main 層不須要 merge
    -merge 操做發生的時機是在 ngx_http_block函數中(即 http 塊解析函數),在遞歸調用 ngx_conf_parse 以後。這是爲了讓http 塊以內全部的指令都解析結束,而後再去作 merge 操做;
  • 不一樣層級塊的邏輯關係,基本上都是放在 ngx_http_core_module 這個模塊的不一樣級別的 conf 中,在 merge中會頻繁用到。

源碼分析

http配置的merge過程是在ngx_http_block()函數中實現,以下:服務器

// 哈哈,扯淡的東西  cmcf = core main conf  我猜的。 
// cscf = core server conf, clcf = core location conf
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

    for (m = 0; ngx_modules[m]; m++) {
// 只對http module才存在merge操做
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
        module = ngx_modules[m]->ctx;
// mi === module index 模塊索引的意思,表示當前module在該類型中的索引
        mi = ngx_modules[m]->ctx_index;

        /* init http{} main_conf's */
        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }
複製代碼

當執行這部分代碼的時候,nginx已經解析完了http的全部配置項,因此纔可以實現merge過程。
從代碼中能夠看出來,會先調用每一個HTTP模塊的 init_main_conf 函數,下面的是 ngx_http_core_module 模塊的鉤子函數,以下:微信

static char *
ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
{
    ngx_http_core_main_conf_t *cmcf = conf;

    if (cmcf->server_names_hash_max_size == NGX_CONF_UNSET_UINT) {
        cmcf->server_names_hash_max_size = 512;
    }

    if (cmcf->server_names_hash_bucket_size == NGX_CONF_UNSET_UINT) {
        cmcf->server_names_hash_bucket_size = ngx_cacheline_size;
    }

    cmcf->server_names_hash_bucket_size =
            ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size);

    if (cmcf->variables_hash_max_size == NGX_CONF_UNSET_UINT) {
        cmcf->variables_hash_max_size = 512;
    }

    if (cmcf->variables_hash_bucket_size == NGX_CONF_UNSET_UINT) {
        cmcf->variables_hash_bucket_size = 64;
    }

    cmcf->variables_hash_bucket_size =
               ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);

    if (cmcf->ncaptures) {
        cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
    }
    return NGX_CONF_OK;
}
複製代碼

上面的函數沒有什麼複雜的地方,就是對 ngx_http_core_,main_conf_t 結構體的一些字段進行初始化。app

下面就是merge的過程了,咱們對代碼精簡一下,以下:函數

    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
        module = ngx_modules[m]->ctx;
// mi === module index 模塊索引的意思,表示當前module在該類型中的索引
        mi = ngx_modules[m]->ctx_index;

        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }
複製代碼

這段代碼的總體邏輯仍是比較簡單的,遍歷全部的HTTP模塊,而後對每一個module都調用 ngx_http_merge_servers()函數,因此真正的merge邏輯是在這個函數中的。
源碼分析

內存佈局
內存佈局
/*
① cf 是代入的參數,可是咱們真正關心的仍是 cf->ctx,這個時候它其實就是 http 級別的三元組(在ngx_http_block函數中賦值)
② cmcf 這個是 http 塊的 ngx_http_core_module 的 main_conf 結構,該結構是全局惟一的,由於不管server級別的ctx仍是location級別的ctx,他們的main_conf都指向了http全局的main_conf
③ module 是個循環獲取的,表明當前遍歷到的HTTP module的ctx
④ mi 就是當前模塊在 NGX_HTTP_MODULE 模塊中的 index

這個函數就實現了當前被遍歷到的http module的server以及location的merge操做
*/

static char *
ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_http_module_t *modulengx_uint_t ctx_index)

{
    char                        *rv;
    ngx_uint_t                   s;
    ngx_http_conf_ctx_t         *ctx, saved;
    ngx_http_core_loc_conf_t    *clcf;
    ngx_http_core_srv_conf_t   **cscfp;

// cscfp: core server conf pointer 指向保存全部server數組的指針
    cscfp = cmcf->servers.elts;
    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
/* 
這裏作了一個保存的操做,由於在下面的代碼中要改變 ctx 中的值,
而且同時使用原始的 ctx。在最後又經過saved變量復原了ctx的值
*/

    saved = *ctx;
    rv = NGX_CONF_OK;
/*
這是第二層循環。對於每個HTTP module,都會遍歷全部的server模塊。
爲何要再循環一次呢?我是這麼理解的:
http {
            instruction_A   value_main_A;
            server {
                # server_1
                instruction_A  value_srv_1;
            }

            server {
                   #server_2
                  instruction_A  value_srv_2
            }
}
上述的instruction既出如今了http  main 級別,又出如今了server級別。而且http配置中有多個server,因此要遍歷全部的server,將每一個server的配置都和main的配置進行合併。
*/

    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->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 (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
                                        cscfp[s]->ctx->loc_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
            /* merge the locations{}' loc_conf's */
            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);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
failed:
    *ctx = saved;
    return rv;
}
複製代碼
簡明示例佈局
簡明示例佈局

這個函數能夠分爲三部分來分析,第一部分,將mainserver級別的配置合併起來。第二部分將serverlocation級別的配置合併起來。第三部分就是location和內嵌location的合併。
1) mainserver級別級別的merge
這裏的ctx_indexngx_http_core_moduleHTTP模塊中的index佈局

 for (s = 0; s < cmcf->servers.nelts; s++) {
        /* merge the server{}s' srv_conf's */
/* 改變 cf->ctx 的 srv_conf,換成當前被遍歷到的 server 塊對應的 srv_conf。*/
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_srv_conf) {
/* 這裏就很明朗了,saved 就是http main級別 的cf->ctx 的內容,那麼它就是 http main級別塊的ctx三元組了,第二個參數也就是當前被遍歷到的HTTP module在 http main級別的ctx->srv_conf數組中對應的 srv_conf,也即上圖中的http_srv_A結構體,也即parent

cscfp[s] 表明對應的當前遍歷的server,而它的 ctx 也就是在解析那個 server 塊的時候建立的三元組。因此第三個參數就是上圖中的server_srv_A,也即child

http{
      // main級別
       root /data0/w3;
       server {
          // server_A
       }

       server {
            // server_B
      }
}
咱們以上面的配置爲例來講明:這一部分代碼會遍歷全部的server塊,也就是會逐個遍歷server_A和server_B。
爲何要遍歷全部的server塊呢?由於要把main級別的配置同步到全部的server塊中。
經過這兩個參數就能夠將main級別的配置項和server級別的配置項合併了.
綜上所述:main和server的merge其實就是http main級別ctx->srv_conf下的結構體和server級別的ctx->srv_conf下的結構體的合併。
和loc_conf下的結構體沒有任何關係。
*/

            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
                                        cscfp[s]->ctx->srv_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
}
複製代碼

2) server級別和location級別配置項的合併
server級別和location級別配置項合併的原理和上面的原理基本相同。都是逐個遍歷的。學習

// 這是第二層循環。對於每個HTTP module,都會遍歷全部的server模塊
    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;
/*
如下圖爲例
        http{
               // main級別
                root /data0/w3;
                server {
                     // server_A
                     location balabala{
                           // location_C
                     }

                   location cilili {
                           // location_D
                    }
               }

               server {
                  // server_B
              }
           }

merge_loc_conf()的第二個參數是當前遍歷到的HTTP module在 main 級別的 ctx->loc_conf數組中的配置,即上圖中的http_loc_A, 是parent級別的location配置。
第三個參數就是 server_A 級別的 ctx->loc_conf[ngx_http_core_module.ctx_index],即上圖中的server_loc_A, 是child級別的location配置。
*/

            rv = module->merge_loc_conf(cf, saved.loc_conf[ctx_index],
                                        cscfp[s]->ctx->loc_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
複製代碼

3) location和內嵌location的合併ui

// 這是第二層循環。對於每個HTTP module,都會遍歷全部的server模塊
    for (s = 0; s < cmcf->servers.nelts; s++) {
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;
        if (module->merge_loc_conf) {
            /* merge the server{}'s loc_conf */
            ctx->loc_conf = cscfp[s]->ctx->loc_conf;

            /* merge the locations{}' loc_conf's */
// clcf 是 server_A的 ctx->loc_conf[ngx_http_core_module.ctx_index]
            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
// 第二個參數: clcf->locations 在server_A這個server塊下面的全部location組成的queue.
// 第三個參數: server_A級別的 ctx->loc_conf 數組。
// 第四個參數:  由於此時是合併ngx_http_core_module的配置項,因此module參數指的是ngx_http_core_module的module_ctx模塊上下文。
// 第五個參數: 由於此時是合併ngx_http_core_module的配置項,因此ctx_index是ngx_http_core_module在全部HTTP module中的ctx_index
            rv = ngx_http_merge_locations(cf, clcf->locations,
                                          cscfp[s]->ctx->loc_conf,
                                          module, ctx_index);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
    }
複製代碼

這裏牽涉到了另外一個函數ngx_http_merge_locations(),以下:

// 第二個參數: locations : clcf->locations 在server_A這個server塊下面的全部location組成的queue.
// 第三個參數: loc_conf : server_A級別的 ctx->loc_conf 數組。
// 第四個參數:  module : 由於此時是合併ngx_http_core_module的配置項,因此module參數指的是ngx_http_core_module的module_ctx模塊上下文。
// 第五個參數: ctx_index : 由於此時是合併ngx_http_core_module的配置項,因此ctx_index是ngx_http_core_module在全部HTTP module中的ctx_index
static char *
ngx_http_merge_locations(ngx_conf_t *cf, ngx_queue_t *locations,
    void **loc_conf, ngx_http_module_t *modulengx_uint_t ctx_index)

{
    char                       *rv;
    ngx_queue_t                *q;
    ngx_http_conf_ctx_t        *ctx, saved;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_location_queue_t  *lq;
 // 若是沒有內嵌的location,則該函數直接返回
    if (locations == NULL) {
        return NGX_CONF_OK;
    }
 /* 這裏代碼看似和 ngx_http_merge_servers 相似,可是差異在於,這個時候 cf->ctx 內
 * 相對應的 srv_conf 和 loc_conf 內容已經被改變爲外層的對應 conf。在
 * ngx_http_merge_servers 中的相關代碼咱們已經分析過了,在這個函數中一樣有相似的代碼。
*/

    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
    saved = *ctx;

    for (q = ngx_queue_head(locations);
         q != ngx_queue_sentinel(locations);
         q = ngx_queue_next(q))
    {
/* 遍歷當前server下面的全部 location,逐個 merge。*/
        lq = (ngx_http_location_queue_t *) q;
        clcf = lq->exact ? lq->exact : lq->inclusive;
/* 改變 cf->ctx 的 loc_conf,換成當前 server 塊對應的 loc_conf。*/
        ctx->loc_conf = clcf->loc_conf;
/*
http{
     server {
          // server_A
           location B {
               // location_B
            }

          location C{
                 // location_C
           }

         location D{
              // location_D
          }
     }
}
   下面的 merge_loc_conf() 的參數上文已經分析過了。這不過這裏是location和內嵌location的合併,咱們再分析一下:
第二個參數: loc_conf[ctx_index] = server_A級別的 ctx->loc_conf[ngx_http_core_module.ctx_index],也就是parent級別的數據。
第三個參數:clcf->loc_conf[ctx_index] = location_B級別的 ctx->loc_conf[ngx_http_core_module.ctx_index],也就是child級別的數據。
*/

        rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
                                    clcf->loc_conf[ctx_index]);
/* server 的時候,它是對應 server 塊的 loc_conf 數組。在下面的代碼中,遞歸調用
 * ngx_http_merge_locations,代入的 loc_conf 就是這一層塊的 loc_conf 數組,因此這裏的
 * loc_conf 其實就表明外層塊的 loc_conf 數組。而 clcf 是很明顯的,是由 locations 隊列
 * 遍歷產生的,也就是表明當前的 location 塊。因此這裏,第二個參數是 parent,第三個參
 * 數是 child。
 */

        if (rv != NGX_CONF_OK) {
            return rv;
        }
 // 嵌套的location
/* 這裏遞歸調用了 ngx_http_merge_locations,嵌套 location 的 merge 操做也能夠成功解決
 * 了,惟一值得注意的就是那些代入的參數,由於進入一層,因此對應的 locations 和
 * loc_conf 也更進了一層。
*/

        rv = ngx_http_merge_locations(cf, clcf->locations, clcf->loc_conf,
                                      module, ctx_index);
        if (rv != NGX_CONF_OK) {
            return rv;
        } 
    }
    *ctx = saved;
    return NGX_CONF_OK;
}
複製代碼

4、總結
整個merge的過程能夠總結以下:
第一層循環:遍歷全部的 NGX_HTTP_MODULE 模塊,對全部module進行調用ngx_http_merge_servers()函數,咱們以 ngx_http_core_module 處理
第二層循環:該層循環在 ngx_http_merge_servers()函數內部,遍歷全部的 server 塊,逐個進行處理
1) 調用 ngx_http_core_modulecreate_srv_conf() 函數對 main 級別的 srv_conf[ctx_index]結構體和各個server級別 srv_conf[ctx_index] 配置結構體合併。
爲何這樣作呢?咱們以client_header_timeout指令爲例:

Defines a timeout for reading client request header. If a client does not transmit the entire header within this time, the request is terminated with the 408 (Request Time-out) error.

這個指令的做用:該指令決定了nginx接收request header的最長時間。若是服務器在指定的時間內沒有接收到完整的request header,那麼這個HTTP請求就會返回408錯誤(該錯誤表示Request Time-out請求超時)。

    { 
      ngx_string("client_header_timeout"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_core_srv_conf_t, client_header_timeout),
      NULL 
    }
複製代碼

client_header_timeout指令,能夠放到mainserver上下文中。從源碼中能夠看出來,client_header_timeout指令的配置數據是存儲到所在級別的 ctx->srv_conf[ngx_http_core_module.ctx_index]中的(由於client_header_timeout的配置指令中offsetNGX_HTTP_SRV_CONF_OFFSET)。

   http {
       client_header_timeout 10s;
       server server_A {
        }

       server server_B {
       client_header_timeout 5s;
     }
}
複製代碼

main級別配置了client_header_timeout指令, 可是server_A 並無配置 client_header_timeout 指令,因此咱們要把 main 級別的配置合併到 server 級別。這樣 server_A 就能夠有本身的 client_header_timeout 配置了。
由於 client_header_timeout 能夠出如今任何的 server 模塊中,因此要遍歷全部的 server,將出如今 main 中的配置項合併到各個server中。
其實將mainserver合併的過程吧:只是針對client_header_timeout這種指令的,由於他們只能出如今main, server級別,而且保存在對應層級的 ctx_srv_conf[ctx_index]中。上面的合併函數傳遞的參數都是各個層級的 srv_conf[ctx_index].

5、參考
blog.csdn.net/weiwangchao…



喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達
鄭爾多斯
鄭爾多斯
相關文章
相關標籤/搜索