- 微信公衆號:鄭爾多斯
- 關注「鄭爾多斯」公衆號 ,回覆「領取資源」,獲取IT資源500G乾貨。
升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峯!想一想還有點小激動- 關注可瞭解更多的
Nginx
知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!
前面的文章已經從源碼級別將配置的解析過程說的很清楚了,本文咱們學習一下nginx
的http
配置的merge
過程。咱們知道nginx
的http
配置結構體是一個很是複雜的東東,一樣的指令可能出如今不一樣的位置,好比root
指令,既能夠出如今http
的main
級別,也能夠出如今server
,location
,if
等上下文中,那麼當一個請求到來的時候,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_conf
和 merge_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 *module, ngx_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;
}
複製代碼
這個函數能夠分爲三部分來分析,第一部分,將main
,server
級別的配置合併起來。第二部分將server
,location
級別的配置合併起來。第三部分就是location
和內嵌location
的合併。
1) main
和server
級別級別的merge
這裏的ctx_index
是ngx_http_core_module
在HTTP
模塊中的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 *module, ngx_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_module
的 create_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
指令,能夠放到main
,server
上下文中。從源碼中能夠看出來,client_header_timeout
指令的配置數據是存儲到所在級別的 ctx->srv_conf[ngx_http_core_module.ctx_index]
中的(由於client_header_timeout
的配置指令中offset
爲 NGX_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
中。
其實將main
和server
合併的過程吧:只是針對client_header_timeout
這種指令的,由於他們只能出如今main
, server
級別,而且保存在對應層級的 ctx_srv_conf[ctx_index]
中。上面的合併函數傳遞的參數都是各個層級的 srv_conf[ctx_index]
.
5、參考
blog.csdn.net/weiwangchao…