### 配置解析接口?
### [](http://tengine.taobao.org/book/chapter_11.html#id3 "永久鏈接至標題")
ngx_init_cycle提供的是配置解析接口。接口是一個切入點,通過少量代碼提供一個完整功能的調用。配置解析接口分為兩個階段,一個是準備階段,另一個就是真正開始調用配置解析。準備階段指什么呢?主要是準備三點:
1. 準備內存
nginx根據以往的經驗(old_cycle)預測這一次的配置需要分配多少內存。比如,我們可以看這段:
if (old_cycle->shared_memory.part.nelts) {
n = old_cycle->shared_memory.part.nelts;
for (part = old_cycle->shared_memory.part.next; part; part = part->next)
{
n += part->nelts;
}
} else {
n = 1;
}
if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
這段代碼的意思是遍歷old_cycle,統計上一次系統中分配了多少塊共享內存,接著就按這個數據初始化當前cycle中共享內存的規模。
1. 準備錯誤日志
nginx啟動可能出錯,出錯就要記錄到錯誤日志中。而錯誤日志本身也是配置的一部分,所以不解析完配置,nginx就不能了解錯誤日志的信息。nginx通過使用上一個周期的錯誤日志來記錄解析配置時發生的錯誤,而在配置解析完成以后,nginx就用新的錯誤日志替換舊的錯誤日志。具體代碼摘抄如下,以說明nginx解析配置時使用old_cycle的錯誤日志:
log = old_cycle->log;
pool->log = log;
cycle->log = log;
1. 準備數據結構
主要是兩個數據結果,一個是ngx_cycle_t結構,一個是ngx_conf_t結構。前者用于存放所有CORE模塊的配置,后者則是用于存放解析配置的上下文信息。具體代碼如下:
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;
}
}
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
準備好了這些內容,nginx開始調用配置解析模塊,其代碼如下:
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
第一個if解析nginx命令行參數’-g’加入的配置。第二個if解析nginx配置文件。好的設計就體現在接口極度簡化,模塊之間的耦合非常低。這里只使用區區10行完成了配置的解析。在這里,我們先淺嘗輒止,具體nginx如何解析配置,我們將在后面的小節做細致的介紹。
[](https://github.com/taobao/nginx-book/issues/new?title=nginx%E7%9A%84%E5%90%AF%E5%8A%A8%E9%98%B6%E6%AE%B5%20(30%25)%C2%B6+%E9%85%8D%E7%BD%AE%E8%A7%A3%E6%9E%90%C2%B6&body=current%20content%3A%0A%20...%0Aadvice%3A%0A%20...%0Areason%3A%0A%20...%0A "點擊提交Issue,反饋你的意見...")
### 配置解析[](http://tengine.taobao.org/book/chapter_11.html#id4 "永久鏈接至標題")
[](http:// "點擊提交Issue,反饋你的意見...")
### 通用過程[](http://tengine.taobao.org/book/chapter_11.html#id5 "永久鏈接至標題")
配置解析模塊在ngx_conf_file.c中實現。模塊提供的接口函數主要是ngx_conf_parse,另外,模塊提供一個單獨的接口ngx_conf_param,用來解析命令行傳遞的配置,當然,這個接口也是對ngx_conf_parse的包裝。
ngx_conf_parse函數支持三種不同的解析環境:
1. parse_file:解析配置文件;
1. parse_block:解析塊配置。塊配置一定是由“{”和“}”包裹起來的;
1. parse_param:解析命令行配置。命令行配置中不支持塊指令。
我們先來鳥瞰nginx解析配置的流程,整個過程可參見下面示意圖:
圖11-2
這是一個遞歸的過程。nginx首先解析core模塊的配置。core模塊提供一些塊指令,這些指令引入其他類型的模塊,nginx遇到這些指令,就重新迭代解析過程,解析其他模塊的配置。這些模塊配置中又有一些塊指令引入新的模塊類型或者指令類型,nginx就會再次迭代,解析這些新的配置類型。比如上圖,nginx遇到“events”指令,就重新調用ngx_conf_parse()解析event模塊配置,解析完以后ngx_conf_parse()返回,nginx繼續解析core模塊指令,直到遇到“http”指令。nginx再次調用ngx_conf_parse()解析http模塊配置的http級指令,當遇到“server”指令時,nginx又一次調用ngx_conf_parse()解析http模塊配置的server級指令。
了解了nginx解析配置的流程,我們來看其中的關鍵函數ngx_conf_parse()。
ngx_conf_parse()解析配置分成兩個主要階段,一個是詞法分析,一個是指令解析。
詞法分析通過ngx_conf_read_token()函數完成。指令解析有兩種方式,其一是使用nginx內建的指令解析機制,其二是使用第三方自定義指令解析機制。自定義指令解析可以參見下面的代碼:
if (cf->handler) {
rv = (*cf->handler)(cf, NULL, cf->handler_conf);
if (rv == NGX_CONF_OK) {
continue;
}
if (rv == NGX_CONF_ERROR) {
goto failed;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
goto failed;
}
這里注意cf->handler和cf->handler_conf兩個屬性,其中handler是自定義解析函數指針,handler_conf是conf指針。
下面著重介紹nginx內建的指令解析機制。本機制分為4個步驟:
1. 只有處理的模塊的類型是NGX_CONF_MODULE或者是當前正在處理的模塊類型,才可能被執行。nginx中有一種模塊類型是NGX_CONF_MODULE,當前只有ngx_conf_module一種,只支持一條指令“include”。“include”指令的實現我們后面再進行介紹。
ngx_modules[i]->type != NGX_CONF_MODULE && ngx_modules[i]->type != cf->module_type
1. 匹配指令名,判斷指令用法是否正確。
1. 指令的Context必須當前解析Context相符;
!(cmd->type & cf->cmd_type)
1. 非塊指令必須以“;”結尾;
!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK
1. 塊指令必須后接“{”;
(cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START
1. 指令參數個數必須正確。注意指令參數有最大值NGX_CONF_MAX_ARGS,目前值為8。
if (!(cmd->type & NGX_CONF_ANY)) {
if (cmd->type & NGX_CONF_FLAG) {
if (cf->args->nelts != 2) {
goto invalid;
}
} else if (cmd->type & NGX_CONF_1MORE) {
if (cf->args->nelts < 2) {
goto invalid;
}
} else if (cmd->type & NGX_CONF_2MORE) {
if (cf->args->nelts < 3) {
goto invalid;
}
} else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {
goto invalid;
} else if (!(cmd->type & argument_number[cf->args->nelts - 1])) {
goto invalid;
}
}
1. 取得指令工作的conf指針。
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[ngx_modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}
}
1. NGX_DIRECT_CONF常量單純用來指定配置存儲區的尋址方法,只用于core模塊。
1. NGX_MAIN_CONF常量有兩重含義,其一是指定指令的使用上下文是main(其實還是指core模塊),其二是指定配置存儲區的尋址方法。所以,在代碼中常常可以見到使用上下文是main的指令的cmd->type屬性定義如下:
NGX_MAIN_CONF|NGX_DIRECT_CONF|...
表示指令使用上下文是main,conf尋址方式是直接尋址。
使用NGX_MAIN_CONF還表示指定配置存儲區的尋址方法的指令有4個:“events”、“http”、“mail”、“imap”。這四個指令也有共同之處——都是使用上下文是main的塊指令,并且塊中的指令都使用其他類型的模塊(分別是event模塊、http模塊、mail模塊和mail模塊)來處理。
NGX_MAIN_CONF|NGX_CONF_BLOCK|...
后面分析ngx_http_block()函數時,再具體分析為什么需要NGX_MAIN_CONF這種配置尋址方式。
1. 除開core模塊,其他類型的模塊都會使用第三種配置尋址方式,也就是根據cmd->conf的值從cf->ctx中取出對應的配置。舉http模塊為例,cf->conf的可選值是NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET,分別對應“http{}”、“server{}”、“location{}”這三個http配置級別。
1. 執行指令解析回調函數
rv = cmd->set(cf, cmd, conf);
cmd是詞法分析得到的結果,conf是上一步得到的配置存貯區地址。
[](http:// "點擊提交Issue,反饋你的意見...")
### http的解析[](http://tengine.taobao.org/book/chapter_11.html#http "永久鏈接至標題")
http是作為一個core模塊被nginx通用解析過程解析的,其核心就是“http”塊指令回調,它完成了http解析的整個功能,從初始化到計算配置結果。
因為這是本書第一次提到塊指令,所以在這里對其做基本介紹。
塊指令的流程是:
1. 創建并初始化上下文環境;
1. 調用通用解析流程解析;
1. 根據解析結果進行后續合并處理;
1. 善后工作。
下面我們以“http”指令為例來介紹這個流程:
[](http:// "點擊提交Issue,反饋你的意見...")
#### 創建并初始化上下文環境[](http://tengine.taobao.org/book/chapter_11.html#id6 "永久鏈接至標題")
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
*(ngx_http_conf_ctx_t **) conf = ctx;
...
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);
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);
}
}
pcf = *cf;
cf->ctx = ctx;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
http模塊的上下文環境ctx(注意我們在通用解析流程中提到的ctx是同一個東西)非常復雜,它是由三個指針數組組成的:main_conf、srv_conf、loc_conf。根據上面的代碼可以看到,這三個數組的元素個數等于系統中http模塊的個數。想想我們平時三四十個http模塊的規模,大家也應該可以理解這一塊結構的龐大。nginx還為每個模塊分別執行對應的create函數分配空間。我們需要注意后面的這一句“cf->ctx = ctx;”,正是這一句將解析配置的上下文切換成剛剛建立的ctx。最后一段代碼通過調用各個http模塊的preconfiguration回調函數完成了對應模塊的預處理操作,其主要工作是創建模塊用到的變量。
[](http:// "點擊提交Issue,反饋你的意見...")
#### 調用通用解析流程解析[](http://tengine.taobao.org/book/chapter_11.html#id7 "永久鏈接至標題")
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
基本上所有的塊指令都類似上面的三行語句(例外是map,它用的是cf->handler),改變通用解析流程的工作狀態,然后調用通用解析流程。
[](http:// "點擊提交Issue,反饋你的意見...")
#### 根據解析結果進行后續合并處理[](http://tengine.taobao.org/book/chapter_11.html#id8 "永久鏈接至標題")
for (m = 0; ngx_modules[m]; m++) {
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
}
rv = ngx_http_merge_servers(cf, cmcf, module, mi);
}
for (s = 0; s < cmcf->servers.nelts; s++) {
if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
for (m = 0; ngx_modules[m]; m++) {
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
if (ngx_http_variables_init_vars(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
return NGX_CONF_ERROR;
}
以上是http配置處理最重要的步驟。首先,在這里調用了各個模塊的postconfiguration回調函數完成了模塊配置過程。更重要的是,它為nginx建立了一棵完整的配置樹(葉子節點為location,包含location的完整配置)、完整的location搜索樹、一張變量表、一張完成的階段處理回調表(phase handler)、一張server對照表和一張端口監聽表。下面我們將分別介紹這些配置表的生成過程。
##### location配置樹[](http://tengine.taobao.org/book/chapter_11.html#location "永久鏈接至標題")
介紹這部分以前,先說明一個nginx的公理
公理11-1:所有存放參數為NGX_HTTP_SRV_CONF_OFFSET的配置,配置僅在請求匹配的虛擬主機(server)上下文中生效,而所有存放參數為NGX_HTTP_LOC_CONF_OFFSET的配置,配置僅在請求匹配的路徑(location)上下文中生效。
正因為有公理11-1,所以nginx需要調用merge_XXX回調函數合并配置。具體的原因是很多配置指令可以放在不同配置層級,比如access_log既可以在http塊中配置,又可以在server塊中配置,還可以在location塊中配置。 但是因為公理11-1,access_log指令配置只有在路徑(location)上下文中生效,所以需要將在http塊中配置的access_log指令的配置向路徑上下文做兩次傳遞,第一次從HTTP(http)上下文到虛擬主機(server)上下文,第二次從虛擬主機上下文到路徑上下文。
可能有人會疑惑,為什么需要傳遞和合并呢?難道它們不在一張表里么?對,在創建并初始化上下文環境的過程中,大家已經看到,nginx為HTTP上下文創建了main_conf,為虛擬主機上下文創建了srv_conf,為路徑上下文創建了loc_conf。但是,這張表只是用于解析在http塊但不包含server塊中定義的指令。而后面我們會看到,在server塊指令中,同樣建立了srv_conf和loc_conf,用于解析在server塊但不含location塊中定義的指令。所以nginx其實維護了很多張配置表,因此nginx必須將配置在這些表中從頂至下不斷傳遞。
前面列出的
for (m = 0; ngx_modules[m]; m++) {
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
}
rv = ngx_http_merge_servers(cf, cmcf, module, mi);
}
就是初始化HTTP上下文,并且完成兩步配置合并操作:從HTTP上下文合并到虛擬主機上下文,以及從虛擬主機上下文合并到路徑上下文。其中,合并到路徑上下問的操作是在ngx_http_merge_servers函數中進行的,見
if (module->merge_loc_conf) {
/* merge the server{}'s loc_conf */
/* merge the locations{}' loc_conf's */
}
大家注意觀察ngx_http_merge_servers函數中的這段,先將HTTP上下文中的location配置合并到虛擬主機上下文,再將虛擬主機上下文中的location配置合并到路徑上下文。
##### location搜索樹[](http://tengine.taobao.org/book/chapter_11.html#id9 "永久鏈接至標題")
公理11-2:nginx搜索路徑時,正則匹配路徑和其他的路徑分開搜。
公理11-3:nginx路徑可以嵌套。
所以,nginx存放location的有兩個指針,分別是
struct ngx_http_core_loc_conf_s {
...
ngx_http_location_tree_node_t *static_locations;
#if (NGX_PCRE)
ngx_http_core_loc_conf_t **regex_locations;
#endif
...
}
通過這段代碼,大家還可以發現一點——nginx的正則表達式需要PCRE支持。
正則表達式的路徑是個指針數組,指針類型就是ngx_http_core_loc_conf_t,所以數據結構決定算法,正則表達式路徑的添加非常簡單,就是在表中插入一項,這里不做介紹。
而其他路徑,保存在ngx_http_location_tree_node_t指針指向的搜索樹static_locations,則是變態復雜,可以看得各位大汗淋漓。
為了說明這棵樹的構建,我們先了解其他路徑包含哪些:
1. 普通前端匹配的路徑,例如location / {}
1. 搶占式前綴匹配的路徑,例如location ^~ / {}
1. 精確匹配的路徑,例如location = / {}
1. 命名路徑,比如location @a {}
1. 無名路徑,比如if {}或者limit_except {}生成的路徑
我們再來看ngx_http_core_loc_conf_t中如何體現這些路徑:
| 普通前端匹配的路徑 | 無 |
|-----|-----|
| 搶占式前綴匹配的路徑 | noregex = 1 |
| 精確匹配的路徑 | exact_match = 1 |
| 命名路徑 | named = 1 |
| 無名路徑 | noname = 1 |
| 正則路徑 | regex != NULL |
有了這些基礎知識,可以看代碼了。首先是ngx_http_init_locations函數
ngx_queue_sort(locations, ngx_http_cmp_locations);
for (q = ngx_queue_head(locations);
q != ngx_queue_sentinel(locations);
q = ngx_queue_next(q))
{
clcf = lq->exact ? lq->exact : lq->inclusive;
if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {
return NGX_ERROR;
}
if (clcf->regex) {
r++;
if (regex == NULL) {
regex = q;
}
continue;
}
if (clcf->named) {
n++;
if (named == NULL) {
named = q;
}
continue;
}
if (clcf->noname) {
break;
}
}
if (q != ngx_queue_sentinel(locations)) {
ngx_queue_split(locations, q, &tail);
}
if (named) {
...
cscf->named_locations = clcfp;
...
}
if (regex) {
...
pclcf->regex_locations = clcfp;
...
}
大家可以看到,這個函數正是根據不同的路徑類型將locations分成多段,并以不同的指針引用。首先注意開始的排序,根據ngx_http_cmp_locations比較各個location,排序以后的順序依次是
1. 精確匹配的路徑和兩類前綴匹配的路徑(字母序,如果某個精確匹配的路徑的名字和前綴匹配的路徑相同,精確匹配的路徑排在前面)
1. 正則路徑(出現序)
1. 命名路徑(字母序)
1. 無名路徑(出現序)
這樣nginx可以簡單的截斷列表得到不同類型的路徑,nginx也正是這樣處理的。
另外還要注意一點,就是ngx_http_init_locations的迭代調用,這里的clcf引用了兩個我們沒有介紹過的字段exact和inclusive。這兩個字段最初是在ngx_http_add_location函數(添加location配置時必然調用)中設置的:
if (clcf->exact_match
#if (NGX_PCRE)
|| clcf->regex
#endif
|| clcf->named || clcf->noname)
{
lq->exact = clcf;
lq->inclusive = NULL;
} else {
lq->exact = NULL;
lq->inclusive = clcf;
}
當然這部分的具體邏輯我們在介紹location解析是再具體說明。
接著我們看ngx_http_init_static_location_trees函數。通過剛才的ngx_http_init_locations函數,留在locations數組里面的還有哪些類型的路徑呢?
還有普通前端匹配的路徑、搶占式前綴匹配的路徑和精確匹配的路徑這三類。
if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
return NGX_ERROR;
}
ngx_http_create_locations_list(locations, ngx_queue_head(locations));
pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);
if (pclcf->static_locations == NULL) {
return NGX_ERROR;
}
請注意除開這段核心代碼,這個函數也有一個自迭代過程。
ngx_http_join_exact_locations函數是將名字相同的精確匹配的路徑和兩類前綴匹配的路徑合并,合并方法
lq->inclusive = lx->inclusive;
ngx_queue_remove(x);
簡言之,就是將前綴匹配的路徑放入精確匹配的路徑的inclusive指針中,然后從列表刪除前綴匹配的路徑。
ngx_http_create_locations_list函數將和某個路徑名擁有相同名稱前綴的路徑添加到此路徑節點的list指針域下,并將這些路徑從locations中摘除。其核心代碼是
ngx_queue_split(&lq->list, x, &tail);
ngx_queue_add(locations, &tail);
ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
ngx_http_create_locations_list(locations, x);
ngx_http_create_locations_tree函數則將剛才劃分的各個list繼續細分,形成一個二分搜索樹,每個中間節點代表一個location,每個location有如下字段:
1. exact:兩類前綴匹配路徑的inclusive指針域指向這兩類路徑的配置上下文;
1. inclusive:精確匹配路徑的exact指針域指向這些路徑的配置上下文;
1. auto_redirect:為各種upstream模塊,比如proxy、fastcgi等等開啟自動URI填充的功能;
1. len:路徑前綴的長度。任何相同前綴的路徑的len等于該路徑名長度減去公共前綴的長度。比如路徑/a和/ab,前者的len為2,后者的len也為1;
1. name:路徑前綴,任何相同前綴的路徑的name是其已于公共前綴的部分。仍舉路徑/a和/ab為例,前者的name為/a,后者的name為b;
1. left:左子樹,當然是長度短或者字母序小的不同前綴的路徑;
1. right:右子樹,當然是長度長或者字母序大的不同前綴的路徑。
通過上面三個步驟,nginx就將locations列表中各種類型的路徑分類處理并由不同的指針引用。對于前綴路徑和精確匹配的路徑,形成一棵獨特的二分前綴樹。
##### 變量表[](http://tengine.taobao.org/book/chapter_11.html#id10 "永久鏈接至標題")
變量表的處理相對簡單,即對照變量名表,為變量表中的每一個元素設置對應的get_handler和data字段。在前面的章節大家已經知道,變量表variables用以處理索引變量,而變量名表variables_keys用于處理可按變量名查找的變量。對于通過ngx_http_get_variable_index函數創建的索引變量,在變量表variables中的get_handler初始為空,如果沒有認為設置的話,將會在這里進行初始化。
特殊變量的get_handler初始化也在這里進行:
| 變量前綴 | get_handler | 標志 |
|-----|-----|-----|
| http | ngx_http_variable_unknown_header_in | ? |
| sent_http | ngx_http_variable_unknown_header_out | ? |
| upstream_http | ngx_http_upstream_header_variable | NGX_HTTP_VAR_NOCACHEABLE |
| cookie | ngx_http_variable_cookie | ? |
| arg | ngx_http_variable_argument | NGX_HTTP_VAR_NOCACHEABLE |
##### 階段處理回調表[](http://tengine.taobao.org/book/chapter_11.html#id11 "永久鏈接至標題")
按照下表順序將各個模塊設置的phase handler依次加入cmcf->phase_engine.handlers列表,各個phase的phase handler的checker不同。checker主要用于限定某個phase的框架邏輯,包括處理返回值。
<table border="1" class="docutils" style="margin: 0px -0.5em; border: 0px;"><colgroup><col width="37%"/><col width="40%"/><col width="23%"/></colgroup><tbody valign="top"><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">處理階段PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">checker</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">可自定義handler</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_POST_READ_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_generic_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_SERVER_REWRITE_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_rewrite_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_FIND_CONFIG_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_find_config_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">否</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_REWRITE_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_rewrite_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_POST_REWRITE_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_post_rewrite_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">否</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_PREACCESS_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_generic_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_ACCESS_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_access_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_POST_ACCESS_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_post_access_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">否</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_TRY_FILES_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_try_files_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">否</td></tr><tr class="row-even"/><tr class="row-odd"><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">NGX_HTTP_CONTENT_PHASE</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">ngx_http_core_content_phase</td><td rowspan="2" style="padding: 1px 8px 1px 5px; border-top-width: 0px; border-right-width: 0px; border-left-width: 0px; border-bottom-color: rgb(170, 170, 170);">是</td></tr><tr class="row-even"/></tbody></table>
注意相同PHASE的phase handler是按模塊順序的反序加入回調表的。另外在NGX_HTTP_POST_REWRITE_PHASE中,ph->next指向NGX_HTTP_FIND_CONFIG_PHASE第一個phase handler,以實現rewrite last邏輯。
##### server對照表[](http://tengine.taobao.org/book/chapter_11.html#server "永久鏈接至標題")
大家如果讀過nginx的“Server names”這篇官方文檔,會了解nginx對于server name的處理分為4中情況:精確匹配、前綴通配符匹配、后綴通配符匹配和正則匹配。那么,下面是又一個公理,
公理11-4:nginx對于不同類型的server name分別處理。
所以,所謂server對照表,其實是四張表,分別對應四種類型的server。數據結構決定算法,四張表決定了nginx必須建立這四張表的行為。鑒于前三種類型和正則匹配可以分成兩大類,nginx使用兩套策略生成server對照表——對正則匹配的虛擬主機名,nginx為其建立一個數組,按照主機名在配置文件的出現順序依次寫入數組;而對于其他虛擬主機名,nginx根據它們的類型為它們分別存放在三張hash表中。三張hash表的結構完全相同,但對于前綴通配或者后綴通配這兩種類型的主機名,nginx對通配符進行的預處理不同。其中“.taobao.com”這種特殊的前綴通配與普通的前綴通配處理又有不同。我們現在來介紹這些不同。
處理前綴通配是將字符串按節翻轉,然后去掉通配符。舉個例子,“*.example.com”會被轉換成“com.example.\0”,而特殊的前綴通配“.example.com”會被轉換成“com.example\0”。
處理后綴通配更簡單,直接去掉通配符。也舉個例子,“www.example.*”會被轉換成“www.example\0”。
##### 端口監聽表[](http://tengine.taobao.org/book/chapter_11.html#id12 "永久鏈接至標題")
對于所有寫在server配置中的listen指令,nginx開始會建立一張server和端口的對照索引表。雖然這不是本節的要點,但要說明索引表到監聽表的轉換過程,還是需要描述其結構。如圖11-3所示,這張索引表是二級索引,第一級索引以listen指定的端口為鍵,第二級索引以listen指定的地址為鍵,索引的對象就是server上下文數據結構。而端口監視表是兩張表,其結構如圖11-4所示。 索引表和監聽表在結構上非常類似,但是卻有一個非常明顯的不同。索引表中第一張表的各表項的端口是唯一的,而監聽表的第一張表中的不同表項的端口卻可能是相同的。之所以出現這樣的差別,是因為nginx會為監聽表第一張表中的每一項分別建立監聽套接字,而在索引表中,如果配置顯式定義了需要監聽不同IP地址的相同端口,它在索引表中會放在同一個端口的二級索引中,而在監聽表中必須存放為
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試