一、Nginx的HTTP過濾模塊特征
一個請求可以被任意個HTTP模塊處理;
在普通HTTP模塊處理請求完畢並調用ngx_http_send_header()發送HTTP頭部或調用ngx_http_output_filter()發送HTTP包體時,才會由這兩個方法一次調用所有的HTTP過濾模塊來處理這個請求。HTTP過濾模塊僅處理服務器發送到客戶端的響應,而不處理客戶端發往服務器的HTTP請求。
多個過濾模塊的順序的形成以及Nginx自帶的過濾模塊請參考原書。
二、編寫一個HTTP過濾模塊
以向返回給用戶的文本格式響應包體前加一段字符串"[my filter prefix]"為例,展示如何編寫一個HTTP過濾模塊。源代碼來自於《深入理解Nginx》。
1.config文件的編寫
與前幾篇博文的HTTP模塊不同,HTTP過濾模塊需要HTTP_FILTER_MODULES一項以把所有過濾模塊一同編譯,因此config寫作:
ngx_addon_name=ngx_http_myfilter_module HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_myfilter_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_myfilter_module.c"
進行configure時,--add-module=PATH是一樣的。
2.編寫模塊基本內容:模塊定義、配置項處理
由於需要在nginx.conf中加入一項flag類型的add_fix來控制這個過濾模塊的使用與否,與這個配置項處理相關的ngx_http_myfilter_create_conf()、ngx_http_myfilter_merge_conf()、ngx_http_mytest_commands[]需要對應地進行處理。
typedef struct { ngx_flag_t enable; } ngx_http_myfilter_conf_t; typedef struct { ngx_int_t add_prefix; } ngx_http_myfilter_ctx_t;

static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf) { ngx_http_myfilter_conf_t *mycf; mycf = (ngx_http_myfilter_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_myfilter_conf_t)); if(mycf == NULL) { return NULL; } mycf->enable = NGX_CONF_UNSET; return mycf; }

static char* ngx_http_myfilter_merge_conf(ngx_conf_t *cf,void *parent, void *child) { ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t *)parent; ngx_http_myfilter_conf_t *conf = (ngx_http_myfilter_conf_t *)child; ngx_conf_merge_value(conf->enable,prev->enable,0); return NGX_CONF_OK; }

static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("add_prefix"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_myfilter_conf_t,enable), NULL }, ngx_null_command };
這樣之后才是模塊的上下文和模塊定義:

static ngx_http_module_t ngx_http_myfilter_module_ctx = { NULL, ngx_http_myfilter_init, NULL, NULL, NULL, NULL, ngx_http_myfilter_create_conf, ngx_http_myfilter_merge_conf };

ngx_module_t ngx_http_myfilter_module = { NGX_MODULE_V1, &ngx_http_myfilter_module_ctx, ngx_http_mytest_commands, NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING };
從模塊上下文可以看出,過濾功能在模塊完成配置項處理后開始,其初始化方法為ngx_myfilter_init()。
3.過濾功能實現
初始化方法ngx_myfilter_init()的功能僅僅是把當前過濾模塊插入Nginx所有過濾模塊的鏈表中。

static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_myfilter_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_myfilter_body_filter; return NGX_OK; }
頭部處理方法是為了確定返回的類型是否為text/plain。如果是,則包體處理方法需要添加前綴。這里把前綴硬編碼至模塊源碼中。

static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r) { ngx_http_myfilter_ctx_t *ctx; ngx_http_myfilter_conf_t *conf; if(r->headers_out.status != NGX_HTTP_OK) { return ngx_http_next_header_filter(r); } ctx = ngx_http_get_module_ctx(r,ngx_http_myfilter_module); if(ctx) { return ngx_http_next_header_filter(r); } conf = ngx_http_get_module_loc_conf(r,ngx_http_myfilter_module); if(conf->enable == 0) { return ngx_http_next_header_filter(r); } ctx = ngx_pcalloc(r->pool,sizeof(ngx_http_myfilter_ctx_t)); if(ctx == NULL) { return NGX_ERROR; } ctx->add_prefix = 0; ngx_http_set_ctx(r,ctx,ngx_http_myfilter_module); if(r->headers_out.content_type.len >= sizeof("text/plain")-1 && ngx_strncasecmp(r->headers_out.content_type.data,(u_char *)"text/plain", sizeof("text/plain")-1) == 0) { ctx->add_prefix = 1; if(r->headers_out.content_length_n > 0) { r->headers_out.content_length_n += filter_prefix.len; } } return ngx_http_myfilter_header_filter(r); }
包體處理方法根據頭部處理方法的結果來為包體添加前綴。

static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_http_myfilter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r,ngx_http_myfilter_module); if(ctx==NULL||ctx->add_prefix != 1) { return ngx_http_next_body_filter(r,in); } ctx->add_prefix = 2; ngx_buf_t* b= ngx_create_temp_buf(r->pool,filter_prefix.len); b->start = b->pos = filter_prefix.data; b->last = b->pos + filter_prefix.len; ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool); c1->buf = b; c1->next = in; return ngx_http_next_body_filter(r,c1); }
三、過濾模塊測試
根據原作者編寫的nginx.conf
server { listen 8080; location / { root /; add_prefix on; } }
可以看出,需要在/目錄下(系統根目錄)添加一個或多個任意內容的文本文件來進行測試。我寫了一個內容為test的文本文件test.txt。
輸入curl http://localhost:8080/test.txt,可以看到返回的內容是[my filter prefix]test。
當然,如果你放在/的不是純文本文件,而是html文件或者其他類型文件,是不會增加這個前綴的。
另外,把on改成off,你會發現前綴不再出現,說明過濾模塊功能已經關閉。
p.s.此書的后續章節是源碼分析,實踐環節比較少,“《深入理解Nginx》閱讀與實踐”系列可能到此為止。