Nginx filter 模塊解析
我們知道nginx很多功能都是通過filter模塊來實現的,如:替換content的sub module、content壓縮的gzip module等。接下去我們看看nginx是怎樣處理filter模塊的。
Nginx filter module所有的代碼都在src\http\module\目錄中,打開可以看到nginx擁有十幾個filter module,以xxx_filter_module.c結尾的源文件便是filter module。
Http請求分爲Header、Content兩部分,粗略的看代碼可以發現,nginx對Http Header、Content使用了分別的filter函數進行處理。舉個例子,我們看gzip filter module的代碼可以發現,ngx_http_gzip_header_filter函數便是nginx gzip對Http Header的處理,同樣ngx_http_gzip_body_filter函數便是對Http Content的處理了。
nginx調用這十幾個filter函數的流程便是本節內容所要討論的。
在源碼配置的過程中,會生成/objs/ngx_modules.c文件,其中定義了一個名爲ngx_modules的數組,其中的內容便是nginx所有的module,源碼爲:
ngx_module_t *ngx_modules[] = {
…………
&ngx_http_proxy_module,
&ngx_http_memcached_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_sub_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};
細心的朋友可能會發現,從ngx_http_write_filter_module開始,下面的全爲filter_module結尾,沒錯,下面以filter_module結尾的便是nginx filter module。
我們首先通過較熟悉的gzip filter module入手,我們先看init函數ngx_http_gzip_filter_init,源碼爲:
static ngx_int_t
ngx_http_gzip_filter_init(ngx_conf_t *cf)
{
// 將 gzip head filter 串到整個鏈表的頭部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_gzip_header_filter;
// 將 gzip body filter 串到整個鏈表的頭部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_gzip_body_filter;
return NGX_OK;
}
代碼其實挺簡單的,做了兩個鏈表串接操作。我們先看header,ngx_http_top_header_filter爲指向整個鏈表頭結點的函數指針(注意:此處爲函數指針),此處將next指針指向頭指針,將頭指針指向gzip header filter,然後在函數ngx_http_gzip_header_filter處理完成後,最終又會調用ngx_http_next_header_filter,這樣就將gzip header filter結點串到鏈表的前面了。Body 處理過程類似,不予重複。
從如上代碼分析可以看出,filter module都是串在鏈表前面的,所以ngx_modules數組中ngx_http_not_modified_filter_module應該是最後一個被串入的,也就是說,此模塊應該在鏈表的最前面。
到此爲止,我們知道nginx所有filter module都是通過鏈表串接起來的,並且數組中最後面的元素在鏈表的第一個結點中。接下去我們看看nginx怎樣將整個鏈表執行起來。
ngx_http_core_module.c中ngx_http_send_header便是整個filter鏈的開始,在其中調用了ngx_http_top_header_filter函數,來啓動整個header filter。同樣ngx_http_output_filter便是啓動整個body filter的函數。當整個head鏈處理完成之後,再行處理body鏈。
我們通過nginx head filter中第一個被處理的filter來仔細分析一下。由於是逆向串入的,所以ngx_http_not_modified_filter_module就是第一個被處理的filter了。
我們還是先看init函數ngx_http_not_modified_filter_init,源碼爲:
static ngx_int_t
ngx_http_not_modified_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
return NGX_OK;
}
非常簡單,僅僅將head filter模塊串入,此模塊沒有body filter,此處分析就先略過去了,正好簡化分析。
我們再看剛纔初始化後的入口函數ngx_http_not_modified_header_filter,源碼爲:
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
if (r->headers_out.status != NGX_HTTP_OK
|| r != r->main
|| r->headers_out.last_modified_time == -1)
{
return ngx_http_next_header_filter(r);
}
// 判斷是否應該返回 Http 412(Precondition Failed)
if (r->headers_in.if_unmodified_since) {
return ngx_http_test_precondition(r);
}
// 判斷是否應該返回 Http 304(Not Modified)
if (r->headers_in.if_modified_since) {
return ngx_http_test_not_modified(r);
}
return ngx_http_next_header_filter(r);
}
代碼也挺簡單,首先判斷Http狀態、一些Flag等信息,如果沒有通過判斷規則,則直接跳入下一個head filter。然後判斷是否沒達到返回條件,即:nginx是否應該返回Http 412(Precondition Failed)狀態碼。然後判斷是否應該返回Http 304(Not Modified)狀態碼。最終如果都沒判斷通過的話,則直接跳入下一個head filter的處理了。
我們做個假設r->headers_in.if_modify_since不爲NULL,則進入ngx_http_test_not_modified,在此函數中,如果執行成功,則將http狀態值設置爲304,最終跳到下一個head filter。
回過頭來再看看,由於此filter僅僅處理Http head信息,無需處理任何Http Content,所以當然就沒有類似 body_filter的函數了。
我們接下去看第二個將要處理的模塊,即ngx_modules中的倒數第二項。在處理倒數第二項時,其僅僅對Http Content做了處理,處理的流程類似於前面的head分析,此處就不詳細解釋了。
最終,我們可以看一下ngx_http_header_filter_module,nginx filter module對Http Header的最後處理。先看init函數ngx_http_header_filter_init,由於這個是第一個結點,在這之前並不存在head filter結點,所以僅僅需要賦值ngx_http_top_header_filter即可。在此head filter中,最終會調用ngx_http_write_filter函數(在ngx_http_header_filter之中)完成整個nginx head filter的處理。
ngx_http_writer_filter_module最終對Http body進行處理,最終會調用send_chain函數(ngx_http_write_filter)完成整個nginx body filter的處理。
到此爲止,我們解釋了整個filter module鏈的串接過程,也做出了實例分析。但有個問題一直未曾解釋,nginx是怎樣調用所有filter module的init函數的,因爲查看代碼知道,每個filter的init函數名不相同,filter module也僅僅存在於一個c文件中,並無對應的頭文件。
本篇開頭,我們列出過一個結構體,其中包含了所有nginx module的指針,而對應於ngx_http_not_modified_filter_module的是最後一個,此結構體最終的定義在ngx_http_not_modified_filter_module.c文件中,源碼爲:
ngx_module_t ngx_http_not_modified_filter_module = {
NGX_MODULE_V1,
&ngx_http_not_modified_filter_module_ctx, /* module context */
NULL, /* 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
};
爲了說明方便,列出 ngx_module_t 的定義:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);
void (*exit_master)(ngx_cycle_t *cycle);
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
可以看到,最終啓用的ngx_module_t的數據項爲 ngx_module_t[index]::ctx(注意NGX_MODULE_V1是一個宏,對應前面7項),此ctx定義爲void*類型,在此filter module中,最終轉化爲類型ngx_http_module_t,其中的數據爲:
typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
static ngx_http_module_t ngx_http_not_modified_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_not_modified_filter_init, /* 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 */
};
至此我們看到了ngx_http_not_modified_filter_init函數,也就是說,這個函數最終可以通過ngx_modules數組來訪問並調用,示例代碼:
ngx_modules[module index]->ctx->postconfiguration(cf);
如上module index假設爲ngx_http_not_modified_filter_module的數組索引,ngx_modules[module index]->ctx便是ngx_http_not_modified_filter_module的ctx,即ngx_http_not_modified_filter_module_ctx。ngx_modules[module index]->ctx的類型爲ngx_http_module_t,可以通過預定義類型來進行訪問,所以ngx_modules[module index]->ctx->postconfiguration(cf);便調用到了最終的init函數。
在ngx_http.c中有函數ngx_http_block,其中便如上進行調用了,源碼片段:
// 循環遍歷整個 ngx_modules 數組
for (m = 0; ngx_modules[m]; m++) {
// 對 module 的類型進行判斷
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
// 臨時時變量 module 指向 ngx_modules[m]->ctx,即 xxx_module_filter_ctx 函數
// 對於 ngx_http_not_modified_filter_module 來說,module 即 ngx_http_not_modified_filter_module_ctx
module = ngx_modules[m]->ctx;
// 判斷並調用 postconfiguration 函數,即 xxx_module_filter_init 函數
// 對於 ngx_http_not_modified_filter_module 來說,即調用了 ngx_http_not_modified_filter_init
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}