nginx http請求的PHASE深度解析
阿新 • • 發佈:2018-12-30
原文出處:http://www.xuebuyuan.com/1722415.html
當客戶端的一個request到達伺服器的時候,可以想象一下,一個伺服器應該怎麼處理這個請求呢?nginx又是怎麼處理請求的呢?
客戶端一個請求到達nginx後,一個worker程序accept後開始處理,首先解析此次請求的請求行(request line),然後處理請求頭(request headers),然後再經過http各功能模組,實現對不同請求的特定處理,最後將result返回給客戶端。
**一:那麼各個模組是怎麼work的呢?**
nginx在這裡採用了PHASE狀態機來實現的,每個phase階段由checker函式和handler函式來控制。先看一下nginx的11個PHASE,有個直觀的印象。
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
上面列舉的就是nginx的所有phase,每個phase只有一個checker,checker是來控制請求階段走向的,通過cheker函式來判斷是繼續在本phase進行下一個handler,還是到下一個phase的handler進行處理,或者直接跳到某個phase的某個handler處理,後續會詳細看一下REWRITE_PHASE的checker函式:ngx_http_core_rewrite_phase 的工作過程。
每個phase都有哪些模組的handler掛載了呢?
當一個nginx程序在處理完請求行和請求頭之後,就會到達phase狀態機的入口函式:ngx_http_core_run_phases
這個函式囊括了整個狀態機的執行。
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/*
ph就儲存了各phase的checker和handler資訊。
while迴圈就是順序執行這些checker,checker是舵手,根據handler處理結果或者相關配置來掌控下一步走法
*/
ph = cmcf->phase_engine.handlers;
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
採用gdb除錯nginx時,列印一下ngx_http_core_run_phases函式中的ph變數,可以看到當前編譯的nginx在phase中註冊的handler,通過handler的名稱我們就能判斷出handler的隸屬模組,下面是筆者所用nginx打印出的ph值。
ph[0]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x48c0c0 <ngx_http_rewrite_handler>, next = 1}
ph[1]= {checker = 0x43b997 <ngx_http_core_find_config_phase>, handler = 0, next = 0}
ph[2]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x4a76a2 <ngx_http_xxx_xxx_handler>, next = 4}//自己開發的第三方擴充套件模組
ph[3]= {checker = 0x43b8e1 <ngx_http_core_rewrite_phase>, handler = 0x48c0c0 <ngx_http_rewrite_handler>, next = 4}
ph[4]= {checker = 0x43bdb5 <ngx_http_core_post_rewrite_phase>, handler = 0, next = 1}
ph[5]= {checker = 0x43b7fe <ngx_http_core_generic_phase>, handler = 0x484b98 <ngx_http_limit_req_handler>, next = 7}
ph[6]= {checker = 0x43b7fe <ngx_http_core_generic_phase>, handler = 0x4837f8 <ngx_http_limit_conn_handler>, next = 7}
ph[7]= {checker = 0x43bf99 <ngx_http_core_access_phase>, handler = 0x483334 <ngx_http_access_handler>, next = 10}
ph[8]= {checker = 0x43bf99 <ngx_http_core_access_phase>, handler = 0x48272c <ngx_http_auth_basic_handler>, next = 10}
ph[9]= {checker = 0x43c16c <ngx_http_core_post_access_phase>, handler = 0, next = 10}
ph[10]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x467eb8 <ngx_http_index_handler>, next = 13}
ph[11]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x480d5c <ngx_http_autoindex_handler>, next = 13}
ph[12]={checker = 0x43cae6 <ngx_http_core_content_phase>, handler = 0x467600 <ngx_http_static_handler>, next = 13}
可見phase狀態機最後的形式是一個指向ngx_http_phase_handler_t結構的指標,cmcf->phase_engine.handlers。
那麼cmcf->phase_engine.handlers這個指標指向的陣列的賦值就成了關鍵。
**二:這些handler是怎麼新增到cmcf->phase_engine.handlers指向的陣列中去的呢?**
先看一下cmcf,cmcf結構是ngx_http_core_main_conf_t
ngx_http_core_main_conf_t有兩個phase相關的成員變數:
typedef struct {
ngx_http_phase_engine_t phase_engine;
ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;
在ngx_http_block函式中,依次呼叫了各模組的 postconfiguration 函式,而玄機就在這些模組的postconfiguration函式裡
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
......
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[m]->ctx;
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;
}
*cf = pcf;
if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
......
}
例如:看一下訪問控制模組ngx_http_access_module的postconfiguration函式
static ngx_http_module_t ngx_http_access_module_ctx = {
NULL, /* preconfiguration */
ngx_http_access_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_access_create_loc_conf, /* create location configuration */
ngx_http_access_merge_loc_conf /* merge location configuration */
};
可以看到ngx_http_access_module模組postconfiguration函式是ngx_http_access_init,在ngx_http_access_init函式中,完成了新增access模組handler的第一步:
static ngx_int_t
ngx_http_access_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
/*將本模組的handler:ngx_http_access_handler放入到NGX_HTTP_ACCESS_PHASE階段。
存在了cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers這個array中
*/
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_access_handler;
return NGX_OK;
}
即呼叫訪問控制模組的postconfiguration函式,將本模組的handler新增到cmcf->phases中NGX_HTTP_ACCESS_PHASE階段的陣列中。
隨著各個模組的postconfiguration函式呼叫,最後,各http模組均將自己的handler註冊到了cmcf->phases中對應的phase陣列中。下面列舉了各PHASE一般包含的http功能模組,“:”左邊是階段(PHASE)名稱,“:”右邊是http功能模組,
POST_READ_PHASE:realip
SERVER_REWRITE_PHASE:rewrite
FIND_CONFIG_PHASE:NULL
REWRITE_PHASE:rewrite
POST_REWRITE_PHASE:NULL
PREACCESS_PHASE:limit_zone、limit_req、realip
ACCESS_PHASE:auth_basic 、access
POST_ACCESS_PHASE:NULL
TRY_FILES_PHASE:NULL
CONTENT_PHASE:index、autoindex、static、dav、gzip_static、random_index
LOG_PHASE:log
需要提到兩個點:
1:為什麼realip存在兩個phase中
POST_READ_PHASE階段的作用範圍為server,使用realip的相關指令,可以對rewrite造成影響
PREACCESS_PHASE階段的作用範圍為location
2:怎麼控制一個phase內執行不同模組執行的handler的順序
執行順序是按照模組的註冊順序的倒序,在進行phase_handlers初始化的時候實現的倒序,這個在後面會提到。
在ngx_http_block依次呼叫所有模組的postconfiguration函式後,11個phase的handler已經註冊完畢,那麼怎麼對應到phase_engine上的呢?
ngx_http_block接著呼叫了函式ngx_http_init_phase_handlers,
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;
//這三個index記錄了rewrite後的下一步,和rewrite命令結尾字元如break,last有關
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用來標註REWRITE_PHASE不將phases[NGX_HTTP_POST_REWRITE_PHASE]陣列中的handler放到phase_engine
,而是進行特殊處理;
use_access用來標註POST_ACCESS_PHASE不將phases[NGX_HTTP_POST_ACCESS_PHASE]陣列中的handler放到phase_engine
,而是進行特殊處理;
同樣的還有try_files階段和find_location階段;
可以看到在接下來的switch程式碼段的時候,case match後continue的階段就是剛提到的進行特殊處理的四個階段
*/
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迴圈就是將phases的handler放到phase_engine陣列中
*/
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;
/*
在這裡採用了倒敘的方法,即同一個phase中先註冊模組的handler會在後面
*/
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;
}
通過程式碼發現,如果要編寫第三方模組的話,根據需求可以將自己模組的handler註冊到某個phase;也可以更改nginx原始碼,支援一個新phase。
但是有四個phase是不支援新增http功能的handler的,這四個階段分別是FIND_CONFIG_PHASE、POST_REWRITE_PHASE、POST_ACCESS_PHASE、TRY_FILES_PHASE。
**三:checker是怎麼工作的呢?**
那個比較有代表性的checker討論討論,NGX_HTTP_POST_REWRITE_PHASE的checker:ngx_http_core_post_rewrite_phase
ngx_http_request_t結構有個成員變數phase_handler標誌著走到了cmcf->phase_engine.handlers陣列的哪一個元素,即到了狀態機的哪一步。
ph = cmcf->phase_engine.handlers;
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
當前request到了狀態機的哪一步,就呼叫對應的checker,假設走到了上面所列狀態機的第五步,r->phase_handler = 4,
ph[4]= {checker = 0x43bdb5 <ngx_http_core_post_rewrite_phase>, handler = 0, next = 1}
那麼就會執行ngx_http_core_post_rewrite_phase這個checker。
注意,每個phase_handler還有一個變數時next,在這裡next=1,表明了requset在某些情況下從當前狀態直接跳到find_config階段
ph[1]= {checker = 0x43b997 <ngx_http_core_find_config_phase>, handler = 0, next = 0}
ngx_int_t
ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
ngx_http_core_srv_conf_t *cscf;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"post rewrite phase: %ui", r->phase_handler);
/*
不管是server_rewrite階段,還是rewrite階段,都會進行url的rewrite重寫,
當使用last的時候,uri_changed=1,當使用break的時候,uri_changed=0;
也就是說當使用break時候,是走到狀態機的下一步。
*/
if (!r->uri_changed) {
r->phase_handler++;
return NGX_AGAIN;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"uri changes: %d", r->uri_changes);
/*
如果使用last,則uri_changes會減去1
uri_changes初始化的值為NGX_HTTP_MAX_URI_CHANGES + 1=11次
也就是說在nginx內部url 的rewrite最多迴圈重定向11次,就會結束這個請求,並報500的錯誤碼
*/
r->uri_changes--;
if (r->uri_changes == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"rewrite or internal redirection cycle "
"while processing \"%V\"", &r->uri);
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_OK;
}
/*
使用last的時候,就會改變狀態機的當前狀態,使得狀態機回到find_config階段,ph->next在這裡指向的就是find_config階段
*/
r->phase_handler = ph->next;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
r->loc_conf = cscf->ctx->loc_conf;
return NGX_AGAIN;
}
一般而言handler返回:
NGX_OK: 表示該階段已經處理完成,需要轉入下一個階段;
NG_DECLINED: 表示需要轉入本階段的下一個handler繼續處理;
NGX_AGAIN, NGX_DONE:
表示需要等待某個事件發生才能繼續處理(比如等待網路IO),此時Nginx為了不阻塞其他請求的處理,必須中斷當前請求的執行鏈,等待事件發生之後繼續執行該handler;
NGX_ERROR: 表示發生了錯誤,需要結束該請求。
checker函式根據handler函式的不同返回值,給上一層的ngx_http_core_run_phases函式返回NGX_AGAIN或者NGX_OK,如果期望上一層繼續執行後面的phase則需要確保checker函式不是返回NGX_OK,不同checker函式對handler函式的返回值處理還不太一樣,開發模組時需要確保相應階段的checker函式對返回值的處理在你的預期之內。