定義自己的HTTP模塊
之前的文章中我們瞭解了定義HTTP模塊時需要定義哪些成員以及實現哪些方法,但在定義HTTP模塊前,首先需要確定自定義的模塊應當在什麼樣的場景下開始處理用戶請求,也就是說,先要弄清楚我們的模塊是如何介入到Nginx處理用戶請求的流程中的。一個HTTP請求會被許多個配置項控制,實際上這是因爲一個HTTP請求可以被許多個HTTP模塊同時處理。這樣一來,肯定會有一個先後問題,也就是說,誰先處理請求誰的“權力”就更大。例如,ngx_http_access_module模塊的deny選項一旦得到滿足後,Nginx就會決定拒絕來自某個IP的請求,後面的諸如root這種訪問靜態文件的處理方式是得不到執行的。另外,由於同一個配置項可以從屬於許多個server、location配置塊,那麼這個配置項將會針對不同的請求起作用。因此,現在面臨的問題是,我們希望自己的模塊在哪個時刻開始處理請求?是希望自己的模塊對到達Nginx的所有請求都起作用,還是希望只對某一類請求(如URI匹配了location後表達式的請求)起作用?
Nginx的HTTP框架定義了非常多的用法,我們有很大的自由來定義自己的模塊如何介入HTTP請求的處理,但本章只想說明最簡單、最常見的HTTP模塊應當如何編寫,因此,我們這樣定義第一個HTTP模塊介入Nginx的方式:
1)不希望模塊對所有的HTTP請求起作用。
2)在nginx.conf文件中的http{}、server{}或者location{}塊內定義mytest配置項,如果一個用戶請求通過主機域名、URI等匹配上了相應的配置塊,而這個配置塊下又具有mytest配置項,那麼希望mytest模塊開始處理請求。
在這種介入方式下,模塊處理請求的順序是固定的,即必須在HTTP框架定義的NGX_HTTP_CONTENT_PHASE階段開始處理請求,具體內容下文詳述。下面開始按照這種方式定義mytest模塊。首先,定義mytest配置項的處理。只需要定義一個ngx_command_t數組,並設置在出現mytest配置後的解析方法由ngx_http_mytest“擔當”,如下所示:
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
其中,ngx_http_mytest是ngx_command_t結構體中的set成員(完整定義爲char*(*set)
(ngx_conf_t*cf,ngx_command_t*cmd,void*conf);),當在某個配置塊中出現mytest配置項時,
Nginx將會調用ngx_http_mytest方法。下面看一下如何實現ngx_http_mytest方法。
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t cmd, void conf) {
ngx_http_core_loc_conf_t *clcf;
/*首先找到mytest配置項所屬的配置塊,clcf看上去像是location塊內的數據結構,其實不然,它可以是main、srv或者loc級別配置項,也就是說,在每個http{}和server{}內也都有一個
ngx_http_core_loc_conf_t結構體
*/
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
/*HTTP框架在處理用戶請求進行到NGX_HTTP_CONTENT_PHASE階段時,如果請求的主機域名、URI與mytest配置項所在的配置塊相匹配,就將調用我們實現的ngx_http_mytest_handler方法處理這個請求
*/
clcf->handler = ngx_http_mytest_handler;
return NGX_CONF_OK;
}
當Nginx接收完HTTP請求的頭部信息時,就會調用HTTP框架處理請求,另外NGX_HTTP_CONTENT_PHASE階段將有可能調用mytest模塊處理請求。在ngx_http_mytest方法中,我們定義了請求的處理方法爲ngx_http_mytest_handler,舉個例子來說,如果用戶的請求URI是/test/example,而在配置文件中有這樣的location塊:
Location /test {
mytest;
}
那麼,HTTP框架在NGX_HTTP_CONTENT_PHASE階段就會調用到我們實現的ngx_http_mytest_handler方法來處理這個用戶請求。事實上,HTTP框架共定義了11個階段,我們目前只關注NGX_HTTP_CONTENT_PHASE處理階段,多數HTTP模塊都在此階段實現相關功能。下面簡單說明一下這11個階段。
typedef enum {
// 在接收到完整的HTTP頭部後處理的HTTP階段
NGX_HTTP_POST_READ_PHASE = 0,
/*在還沒有查詢到URI匹配的location前,這時rewrite重寫URL也作爲一個獨立的HTTP階段
*/
NGX_HTTP_SERVER_REWRITE_PHASE,
/*根據URI尋找匹配的location,這個階段通常由ngx_http_core_module模塊實現,不建議其他HTTP模塊重新定義這一階段的行爲
*/
NGX_HTTP_FIND_CONFIG_PHASE,
/*在NGX_HTTP_FIND_CONFIG_PHASE階段之後重寫URL的意義與NGX_HTTP_SERVER_REWRITE_PHASE階段顯然是不同的,因爲這兩者會導致查找到不同的location塊(location是與URI進行匹配的)
*/
NGX_HTTP_REWRITE_PHASE,
/*這一階段是用於在rewrite重寫URL後重新跳到NGX_HTTP_FIND_CONFIG_PHASE階段,找到與新的URI匹配的location。所以,這一階段是無法由第三方HTTP模塊處理的,而僅由ngx_http_core_module模塊使用
*/
NGX_HTTP_POST_REWRITE_PHASE,
// 處理NGX_HTTP_ACCESS_PHASE階段前,HTTP模塊可以介入的處理階段
NGX_HTTP_PREACCESS_PHASE,
// 這個階段用於讓HTTP模塊判斷是否允許這個請求訪問Nginx服務器
NGX_HTTP_ACCESS_PHASE,
/*當NGX_HTTP_ACCESS_PHASE階段中HTTP模塊的handler處理方法返回不允許訪問的錯誤碼時(實際是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),這個階段將負責構造拒絕服務的用戶響應。所以,這個階段實際上用於給NGX_HTTP_ACCESS_PHASE階段收尾
*/
NGX_HTTP_POST_ACCESS_PHASE,
/*這個階段完全是爲了try_files配置項而設立的。當HTTP請求訪問靜態文件資源時,try_files配置項可以使這個請求順序地訪問多個靜態文件資源,如果某一次訪問失敗,則繼續訪問try_files中指定的下一個靜態資源。另外,這個功能完全是在NGX_HTTP_TRY_FILES_PHASE階段中實現的
*/
NGX_HTTP_TRY_FILES_PHASE,
// 用於處理HTTP請求內容的階段,這是大部分HTTP模塊最喜歡介入的階段
NGX_HTTP_CONTENT_PHASE,
/*處理完請求後記錄日誌的階段。例如,ngx_http_log_module模塊就在這個階段中加入了一個handler處理方法,使得每個HTTP請求處理完畢後會記錄access_log日誌
*/
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
暫且不管如何實現處理請求的ngx_http_mytest_handler方法,如果沒有什麼工作是必須在HTTP框架初始化時完成的,那就不必實現ngx_http_module_t的8個回調方法,可以像下面這樣定義ngx_http_module_t接口。
static ngx_http_module_t ngx_http_mytest_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
最後,定義mytest模塊:
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx, /* module context */
ngx_http_mytest_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
這樣,mytest模塊在編譯時將會被加入到ngx_modules全局數組中。Nginx在啓動時,會調
用所有模塊的初始化回調方法,當然,這個例子中我們沒有實現它們(也沒有實現HTTP框
架初始化時會調用的ngx_http_module_t中的8個方法)。