### 多階段執行鏈[](http://tengine.taobao.org/book/chapter_12.html#id9 "永久鏈接至標題")
nginx按請求處理的執行順序將處理流程劃分為多個階段,一般每個階段又可以注冊多個模塊處理函數,nginx按階段將這些處理函數組織成了一個執行鏈,這個執行鏈保存在http主配置(ngx_http_core_main_conf_t)的phase_engine字段中,phase_engine字段的類型為ngx_http_phase_engine_t:
[](http:// "點擊提交Issue,反饋你的意見...")
typedef struct {
ngx_http_phase_handler_t *handlers;
ngx_uint_t server_rewrite_index;
ngx_uint_t location_rewrite_index;
} ngx_http_phase_engine_t;
其中handlers字段即為執行鏈,實際上它是一個數組,而每個元素之間又被串成鏈表,從而允許執行流程向前,或者向后的階段跳轉,執行鏈節點的數據結構定義如下:
[](http:// "點擊提交Issue,反饋你的意見...")
struct ngx_http_phase_handler_s {
ngx_http_phase_handler_pt checker;
ngx_http_handler_pt handler;
ngx_uint_t next;
};
其中checker和handler都是函數指針,相同階段的節點具有相同的checker函數,handler字段保存的是模塊處理函數,一般在checker函數中會執行當前節點的handler函數,但是例外的是NGX_HTTP_FIND_CONFIG_PHASE,NGX_HTTP_POST_REWRITE_PHASE,NGX_HTTP_POST_ACCESS_PHASE和NGX_HTTP_TRY_FILES_PHASE這4個階段不能注冊模塊函數。next字段為快速跳躍索引,多數情況下,執行流程是按照執行鏈順序的往前執行,但在某些執行階段的checker函數中由于執行了某個邏輯可能需要回跳至之前的執行階段,也可能需要跳過之后的某些執行階段,next字段保存的就是跳躍的目的索引。
和建立執行鏈相關的數據結構都保存在http主配置中,一個是phases字段,另外一個是phase_engine字段。其中phases字段為一個數組,它的元素個數等于階段數目,即每個元素對應一個階段。而phases數組的每個元素又是動態數組(ngx_array_t),每次模塊注冊處理函數時只需要在對應階段的動態數組增加一個元素用來保存處理函數的指針。由于在某些執行階段可能需要向后,或者向前跳轉,簡單的使用2個數組并不方便,所以nginx又組織了一個執行鏈,保存在了phase_engine字段,其每個節點包含一個next域用來保存跳躍目的節點的索引,而執行鏈的建立則在nginx初始化的post config階段之后調用ngx_http_init_phase_handlers函數完成,下面分析一下該函數:
[](http:// "點擊提交Issue,反饋你的意見...")
static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
ngx_int_t j;
ngx_uint_t i, n;
ngx_uint_t find_config_index, use_rewrite, use_access;
ngx_http_handler_pt *h;
ngx_http_phase_handler_t *ph;
ngx_http_phase_handler_pt checker;
cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
find_config_index = 0;
use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
n += cmcf->phases[i].handlers.nelts;
}
ph = ngx_pcalloc(cf->pool,
n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
if (ph == NULL) {
return NGX_ERROR;
}
cmcf->phase_engine.handlers = ph;
n = 0;
for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
h = cmcf->phases[i].handlers.elts;
switch (i) {
case NGX_HTTP_SERVER_REWRITE_PHASE:
if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
cmcf->phase_engine.server_rewrite_index = n;
}
checker = ngx_http_core_rewrite_phase;
break;
case NGX_HTTP_FIND_CONFIG_PHASE:
find_config_index = n;
ph->checker = ngx_http_core_find_config_phase;
n++;
ph++;
continue;
case NGX_HTTP_REWRITE_PHASE:
if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
cmcf->phase_engine.location_rewrite_index = n;
}
checker = ngx_http_core_rewrite_phase;
break;
case NGX_HTTP_POST_REWRITE_PHASE:
if (use_rewrite) {
ph->checker = ngx_http_core_post_rewrite_phase;
ph->next = find_config_index;
n++;
ph++;
}
continue;
case NGX_HTTP_ACCESS_PHASE:
checker = ngx_http_core_access_phase;
n++;
break;
case NGX_HTTP_POST_ACCESS_PHASE:
if (use_access) {
ph->checker = ngx_http_core_post_access_phase;
ph->next = n;
ph++;
}
continue;
case NGX_HTTP_TRY_FILES_PHASE:
if (cmcf->try_files) {
ph->checker = ngx_http_core_try_files_phase;
n++;
ph++;
}
continue;
case NGX_HTTP_CONTENT_PHASE:
checker = ngx_http_core_content_phase;
break;
default:
checker = ngx_http_core_generic_phase;
}
n += cmcf->phases[i].handlers.nelts;
for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
ph->checker = checker;
ph->handler = h[j];
ph->next = n;
ph++;
}
}
return NGX_OK;
}
首先需要說明的是cmcf->phases數組中保存了在post config之前注冊的所有模塊函數,上面的函數先計算執行鏈的節點個數,并分配相應的空間,前面提到有4個階段不能注冊模塊,并且POST_REWRITE和POST_ACCESS這2個階段分別只有在REWRITE和ACCESS階段注冊了模塊時才存在,另外TRY_FILES階段只有在配置了try_files指令的時候才存在,最后FIND_CONFIG階段雖然不能注冊模塊,但它是必須存在的,所以在計算執行鏈節點數時需要考慮這些因素。
分配好內存之后,開始建立鏈表,過程很簡單,遍歷每個階段注冊的模塊函數,為每個階段的節點賦值checker函數,handler函數,以及next索引。最終建立好的執行鏈如下圖:
(暫缺)
SERVER_REWRITE階段的節點的next域指向FIND_CONFIG階段的第1個節點,REWRITE階段的next域指向POST_REWRITE階段的第1個節點,而POST_REWRITE階段的next域則指向FIND_CONFIG,因為當出現location級別的uri重寫時,可能需要重新匹配新的location,PREACCESS階段的next域指向ACCESS域,ACCESS和POST_ACCESS階段的next域則是則是指向CONTENT階段,當然如果TRY_FILES階段存在的話,則是指向TRY_FILES階段,最后CONTENT階段的next域指向LOG階段,當然next域是每個階段的checker函數根據該階段的需求來使用的,沒有需要時,checker函數可能都不會使用到它。
- 上篇: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 模塊編譯,調試與測試