Nginx开发一个简单的HTTP过滤模块
本文將學(xué)些開發(fā)一個(gè)簡單的HTTP過濾模塊,它能夠?qū)ontent-Type為text/plain的包體前加上前綴字符串prefix.
<一> 過濾模塊的調(diào)用順序
過濾模塊可以疊加,也就是說一個(gè)請求會被所有的HTTP過濾模塊依次處理。
過濾模塊的調(diào)用時(shí)有順序的,它的順序在編譯的時(shí)候就決定了。控制編譯的腳本位于auto/modules中,當(dāng)你編譯完Nginx以后,可以再objs目錄下面看到一個(gè)ngx_modules.c的文件,打開這個(gè)文件,有類似的代碼:
ngx_module_t *ngx_modules[] = {...&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_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 };從write_filter到not_modified_filter,模塊的執(zhí)行順序是反向的。也就是說最早執(zhí)行的是not_modified_filter,然后各個(gè)模塊以此執(zhí)行。所有第三方的模塊只能加入到copy_filter和headers_filter模塊之間執(zhí)行。
在編譯Nginx源碼時(shí),已經(jīng)定義了一個(gè)由所有HTTP過濾模塊組成的單鏈表,這個(gè)單鏈表是這樣的:
鏈表的每個(gè)元素都是一個(gè)C源代碼文件,這個(gè)C源代碼文件中有兩個(gè)指針,分別指向下一個(gè)過濾模塊(文件)的過濾頭部和包體的方法(可理解為鏈表中的next指針)過濾模塊單鏈表示意圖:
這兩個(gè)指針的聲明如下:
/*過濾模塊處理HTTP頭部的函數(shù)指針類型定義,它攜帶一個(gè)參數(shù):請求*/ typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r); /*過濾模塊處理HTTP包體的函數(shù)指針類型定義,它攜帶兩個(gè)參數(shù):請求、要發(fā)送的包體*/ typedef ngx_int_t (*ngx_http_output_body_filter_pt) (ngx_http_request_t *r, ngx_chain_t *chain);在我們定義的第三方模塊中則有如下聲明:
static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter;那么怎么將這個(gè)源文件(節(jié)點(diǎn)),插入到HTTP過濾模塊組成的單鏈表中去呢?Nginx采用頭插法的辦法,所有的新節(jié)點(diǎn)都插入在鏈表的開頭:
//插入到頭部處理方法鏈表的首部 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;其中,兩個(gè)top指針聲明如下:
extern ngx_http_output_header_filter_pt ngx_http_next_header_filter; extern ngx_http_output_body_filter_pt ngx_http_next_body_filter;由于是頭插法,這樣就解釋了,越早插入鏈表的過濾模塊,就會越晚執(zhí)行。
<二> 開發(fā)一個(gè)簡單的過濾模塊
要開發(fā)一個(gè)簡單的過濾模塊,它的功能是對Content-Type為text/plain的響應(yīng)添加一個(gè)前綴,類似于開發(fā)一個(gè)HTTP模塊,它應(yīng)該遵循如下步驟:
1.確定源代碼文件名稱,源代碼所在目錄創(chuàng)建config腳本文件,config文件的編寫方式跟HTTP模塊開發(fā)基本一致,不同的是需要將HTTP_MODULES改成HTTP_FILTER_MODULES. 2.定義過濾模塊。實(shí)例化ngx_module_t類型模塊結(jié)構(gòu),因?yàn)镠TTP過濾模塊也是HTTP模塊,所以其中的type成員也是NGX_HTTP_MODULE. 3.處理感興趣的配置項(xiàng),通過設(shè)置ngx_module_t中的ngx_command_t數(shù)組來處理感興趣的配置項(xiàng)。 4.實(shí)現(xiàn)初始化方法。初始化方法就是把本模塊中處理HTTP頭部的ngx_http_output_header_filter_pt方法和處理HTTP包體的ngx_http_output_body_filter_pt方法插入到過濾模塊鏈表的首部。 5.實(shí)現(xiàn)4.中提到兩個(gè)處理頭部和包體的方法。接下來按照上述步驟依次來實(shí)現(xiàn):
2.1 確定源代碼文件目錄,編寫config文件
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"2.2 定義過濾模塊,實(shí)例化ngx_module_t
/*定義過濾模塊,ngx_module_t結(jié)構(gòu)體實(shí)例化*/ ngx_module_t ngx_http_myfilter_module = {NGX_MODULE_V1, /*Macro*/&ngx_http_myfilter_module_ctx, /*module context*/ngx_http_myfilter_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 /*Macro*/ };2.3 處理感興趣的配置項(xiàng)
/*處理感興趣的配置項(xiàng)*/ static ngx_command_t ngx_http_myfilter_commands[] = {{ngx_string("add_prefix"), //配置項(xiàng)名稱NGX_HTTP_MAIN_CONF| NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_FLAG, //配置項(xiàng)只能攜帶一個(gè)參數(shù)并且是on或者offngx_conf_set_flag_slot, //使用nginx自帶方法,參數(shù)on/offNGX_HTTP_LOC_CONF_OFFSET, //使用create_loc_conf方法產(chǎn)生的結(jié)構(gòu)體來存儲//解析出來的配置項(xiàng)參數(shù)offsetof(ngx_http_myfilter_conf_t, enable),//on/offNULL},ngx_null_command };? 其中定義結(jié)構(gòu)體ngx_http_myfilter_conf_t來保存配置項(xiàng)參數(shù)typedef struct {ngx_flag_t enable; } ngx_http_myfilter_conf_t;
2.4 實(shí)現(xiàn)初始化方法
頭插入法將本過濾模塊插入到單鏈表的首部:
/*初始化方法*/ 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; }2.5 實(shí)現(xiàn)頭部和包體過濾方法
? 2.5.1 函數(shù)聲明
/*頭部處理方法*/ static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r);/*包體處理方法*/ static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);? 2.5.2 函數(shù)實(shí)現(xiàn)
? (1) 頭處理方法:最終處理效果頭部信息Content-Length的值加上prefix的長度。
/*頭部處理方法*/ 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;//如果不是返回成功,這時(shí)是不需要理會是否加前綴的,直接交由下一個(gè)過濾模塊,處理響應(yīng)碼非200的情形if (r->headers_out.status != NGX_HTTP_OK) {return ngx_http_next_header_filter(r);}/*獲取HTTP上下文*/ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);if (ctx) {//該請求的上下文已經(jīng)存在,這說明ngx_http_myfilter_header_filter已經(jīng)被調(diào)用過1次,直接交由下一個(gè)過濾模塊處理return ngx_http_next_header_filter(r);}//獲取存儲配置項(xiàng)參數(shù)的結(jié)構(gòu)體conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);//如果enable成員為0,也就是配置文件中沒有配置add_prefix配置項(xiàng),或者add_prefix配置下情的參數(shù)值是off,這時(shí)直接交由下一個(gè)過濾模塊處理if(conf->enable == 0) {return ngx_http_next_header_filter(r);}//conf->enable==1//構(gòu)造http上下文結(jié)構(gòu)體ngx_http_myfilter_ctx_tctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));if (NULL == ctx) {return NGX_ERROR;}ctx->add_prefix = 0;ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);//只處理Content-Type是"text/plain"類型的http響應(yīng)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;}}//交由下一個(gè)過濾模塊繼續(xù)處理return ngx_http_next_header_filter(r); }? (2) 響應(yīng)包體處理方法:最終處理效果在包體前面添加前綴。/*包體處理方法*/ 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);//如果獲取不到上下文,或者上下文結(jié)構(gòu)體中的add_prefix為0或者2時(shí),都不會添加前綴,這時(shí)直接交給下一個(gè)http過濾模塊處理if (ctx == NULL || ctx->add_prefix != 1) {return ngx_http_next_body_filter(r, in);}//將add_prefix設(shè)置為2,這樣即使ngx_http_myfilter_body_filter再次回調(diào)時(shí),也不會重復(fù)添加前綴ctx->add_prefix = 2;//從請求的內(nèi)存池中分配內(nèi)存,用于存儲字符串前綴ngx_buf_t *b = ngx_create_temp_buf(r->pool, filter_prefix.len);//將ngx_buf_t 中的指針正確地指向filter_prefix字符串b->start = b->pos = filter_prefix.data;b->last = b->pos + filter_prefix.len;//從請求的內(nèi)存池中生成Ngx_chain_t鏈表,將剛分配的ngx_buf_t設(shè)置到其buf成員中,并將它添加到原先待發(fā)送的http包體前面ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);/*note: in表示原來待發(fā)送的包體*/cl->buf = b;cl->next = in;//調(diào)用下一個(gè)模塊的htpp包體處理方法,注意這時(shí)傳入的是新生成的cl鏈表return ngx_http_next_body_filter(r, cl); }
(三) 完整代碼與測試
? 至此,已經(jīng)完成大部分的工作,我們還需要為這個(gè)過濾模塊編寫模塊上下文,編寫創(chuàng)建和合并配置項(xiàng)參數(shù)結(jié)構(gòu)體的函數(shù)等。
? 3.1 完整代碼
/*ngx_http_myfilter_module.c*/#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h>/*用static修飾只在本文件生效,因此允許所有的過濾模塊都有自己的這兩個(gè)指針*/ 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);/*頭部處理方法*/ static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r);/*包體處理方法*/ static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);typedef struct {ngx_flag_t enable; }ngx_http_myfilter_conf_t;/*請求上下文*/ typedef struct {ngx_int_t add_prefix; }ngx_http_myfilter_ctx_t;/*在包體中添加的前綴*/ static ngx_str_t filter_prefix=ngx_string("[my filter prefix]");/*處理感興趣的配置項(xiàng)*/ static ngx_command_t ngx_http_myfilter_commands[]= {{ngx_string("add_prefix"), //配置項(xiàng)名稱NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_FLAG,//配置項(xiàng)只能攜帶一個(gè)參數(shù)并且是on或者offngx_conf_set_flag_slot,//使用nginx自帶方法,參數(shù)on/offNGX_HTTP_LOC_CONF_OFFSET,//使用create_loc_conf方法產(chǎn)生的結(jié)構(gòu)體來存儲//解析出來的配置項(xiàng)參數(shù)offsetof(ngx_http_myfilter_conf_t, enable),//on/offNULL},ngx_null_command // };static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf); static char* ngx_http_myfilter_merge_conf(ngx_conf_t *cf,void*parent,void*child);/*模塊上下文*/ static ngx_http_module_t ngx_http_myfilter_module_ctx= {NULL, /* preconfiguration方法 */ngx_http_myfilter_init, /* postconfiguration方法 */NULL, /*create_main_conf 方法 */NULL, /* init_main_conf方法 */NULL, /* create_srv_conf方法 */NULL, /* merge_srv_conf方法 */ngx_http_myfilter_create_conf, /* create_loc_conf方法 */ngx_http_myfilter_merge_conf /*merge_loc_conf方法*/ };/*定義過濾模塊,ngx_module_t結(jié)構(gòu)體實(shí)例化*/ ngx_module_t ngx_http_myfilter_module = {NGX_MODULE_V1, /*Macro*/&ngx_http_myfilter_module_ctx, /*module context*/ngx_http_myfilter_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 /*Macro*/ };static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf) {ngx_http_myfilter_conf_t *mycf;//創(chuàng)建存儲配置項(xiàng)的結(jié)構(gòu)體mycf = (ngx_http_myfilter_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_myfilter_conf_t));if (mycf == NULL){return NULL;}//ngx_flat_t類型的變量,如果使用預(yù)設(shè)函數(shù)ngx_conf_set_flag_slot//解析配置項(xiàng)參數(shù),必須初始化為NGX_CONF_UNSETmycf->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_flat_t類型的配置項(xiàng)enablengx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK;}/*初始化方法*/ 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; }/*頭部處理方法*/ 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;//如果不是返回成功,這時(shí)是不需要理會是否加前綴的,//直接交由下一個(gè)過濾模塊//處理響應(yīng)碼非200的情形if (r->headers_out.status != NGX_HTTP_OK){return ngx_http_next_header_filter(r);}/*獲取http上下文*/ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);if(ctx){//該請求的上下文已經(jīng)存在,這說明// ngx_http_myfilter_header_filter已經(jīng)被調(diào)用過1次,//直接交由下一個(gè)過濾模塊處理return ngx_http_next_header_filter(r);}//獲取存儲配置項(xiàng)參數(shù)的結(jié)構(gòu)體conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);//如果enable成員為0,也就是配置文件中沒有配置add_prefix配置項(xiàng),//或者add_prefix配置項(xiàng)的參數(shù)值是off,這時(shí)直接交由下一個(gè)過濾模塊處理if (conf->enable == 0){return ngx_http_next_header_filter(r);}//conf->enable==1//構(gòu)造http上下文結(jié)構(gòu)體ngx_http_myfilter_ctx_tctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));if(NULL==ctx){return NGX_ERROR;}ctx->add_prefix=0;ngx_http_set_ctx(r,ctx,ngx_http_myfilter_module);//只處理Content-Type是"text/plain"類型的http響應(yīng)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;}}//交由下一個(gè)過濾模塊繼續(xù)處理return ngx_http_next_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);//如果獲取不到上下文,或者上下文結(jié)構(gòu)體中的add_prefix為0或者2時(shí),//都不會添加前綴,這時(shí)直接交給下一個(gè)http過濾模塊處理if (ctx == NULL || ctx->add_prefix != 1){return ngx_http_next_body_filter(r, in);}//將add_prefix設(shè)置為2,這樣即使ngx_http_myfilter_body_filter//再次回調(diào)時(shí),也不會重復(fù)添加前綴ctx->add_prefix = 2;//從請求的內(nèi)存池中分配內(nèi)存,用于存儲字符串前綴ngx_buf_t* b = ngx_create_temp_buf(r->pool, filter_prefix.len);//將ngx_buf_t中的指針正確地指向filter_prefix字符串b->start = b->pos = filter_prefix.data;b->last = b->pos + filter_prefix.len;//從請求的內(nèi)存池中生成ngx_chain_t鏈表,將剛分配的ngx_buf_t設(shè)置到//其buf成員中,并將它添加到原先待發(fā)送的http包體前面ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);/*note: in表示原來待發(fā)送的包體*/cl->buf = b;cl->next = in;//調(diào)用下一個(gè)模塊的http包體處理方法,注意這時(shí)傳入的是新生成的cl鏈表return ngx_http_next_body_filter(r, cl); }? 3.2 測試
? 我們的過濾模塊只對Content-Type為text/plain的響應(yīng)有效,查看Nginx的默認(rèn)配置中的mime.types文件,發(fā)現(xiàn)
types{ #...text/plain txt; #... }? 也即當(dāng)請求資源位txt時(shí)才會調(diào)用我們過濾模塊,如果想要強(qiáng)制將響應(yīng)的Content-Type設(shè)置為text/plain呢?
? 只需要修改nginx.conf文件如下即可:
#user root; worker_processes 1;error_log logs/error.log debug;events {worker_connections 1024; }http { # 注釋掉http塊下的配置 # include mime.types; # default_type application/octet-stream;keepalive_timeout 65;server {listen 1024;location / {#在location塊下將默認(rèn)類型設(shè)置為text/plaindefault_type text/plain;root html;add_prefix on;index index.htm index.html;}} }? 將自定義的顧慮模塊編譯進(jìn)Nginx:
./configure --add-module=/home/zhangxiao/nginx/nginx-1.0.15/src/myHttpFilterModule/ make;sudo make install? 重啟nginx,用curl工具進(jìn)行測試:
curl -v localhost:1024? 可以看到返回的包體添加了前綴:
??
參考鏈接:
https://blog.csdn.net/zhangxiao93/article/details/53691642#%E4%B8%80%E8%BF%87%E6%BB%A4%E6%A8%A1%E5%9D%97%E7%9A%84%E8%B0%83%E7%94%A8%E9%A1%BA%E5%BA%8F
總結(jié)
以上是生活随笔為你收集整理的Nginx开发一个简单的HTTP过滤模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pcre函数详解
- 下一篇: 记录ishield遇到的问题的解决过程