一個請求能夠被任意個HTTP模塊處理;html
在普通HTTP模塊處理請求完畢並調用ngx_http_send_header()發送HTTP頭部或調用ngx_http_output_filter()發送HTTP包體時,纔會由這兩個方法一次調用全部的HTTP過濾模塊來處理這個請求。HTTP過濾模塊僅處理服務器發送到客戶端的響應,而不處理客戶端發往服務器的HTTP請求。nginx
多個過濾模塊的順序的造成以及Nginx自帶的過濾模塊請參考原書。服務器
以向返回給用戶的文本格式響應包體前加一段字符串"[my filter prefix]"爲例,展現如何編寫一個HTTP過濾模塊。源代碼來自於《深刻理解Nginx》。curl
與前幾篇博文的HTTP模塊不一樣,HTTP過濾模塊須要HTTP_FILTER_MODULES一項以把全部過濾模塊一同編譯,所以config寫做:ide
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是同樣的。源碼分析
因爲須要在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()。url
初始化方法ngx_myfilter_init()的功能僅僅是把當前過濾模塊插入Nginx全部過濾模塊的鏈表中。spa
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》閱讀與實踐」系列可能到此爲止。