nginx 學習五 filter模塊簡介和實現一個簡單的filter模塊

1 nginx過濾模塊簡介

過濾(filter)模塊是過濾響應頭和內容的模塊,可以對回覆的頭和內容進行處理。它的處理時間在獲取回覆內容之後,向用戶發送響應之前。它的處理過程分爲兩個階段,過濾HTTP回覆的頭部和主體,在這兩個階段可以分別對頭部和主體進行修改。

2 過濾模塊執行順序

2.1 ngx_http_output_(head, body)_filter_pt

先看一下nginx常用的過濾模塊,在ngx_moudles.c中有一下代碼:

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_myfilter_module,
    &ngx_http_headers_filter_module,
    &ngx_http_copy_filter_module,
    &ngx_http_range_body_filter_module,
    &ngx_http_not_modified_filter_module,
    NULL
};

nginx過濾模塊的執行順序是從ngx_http_not_modified_filter_module 到 ngx_http_write_filter_module。下面我們來看一下這個執行順序是怎麼用鏈表構成的。

既然是用鏈表構成的,肯定是要有鏈表指針,而且有兩個,一個鏈接處理頭部的函數,一個鏈接處理包體的函數,下面來看看這兩個鏈表指針的定義:

typedef ngx_int_t (ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
typedef ngx_int_t (ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *in);

ngx_http_output_header_filter_pt 指向處理頭部的函數指針,每一個過濾模塊中如果要處理頭部,就必要定義一個局部的這種類型的指針和函數。

ngx_http_output_body_filter_pt 指向處理包體的函數指針,每一個過濾模塊中如果要處理包體,就必要定義一個局部的這種類型的指針和函數。

2.2 過濾模塊鏈表的入口

過濾模塊鏈表的入口是兩個靜態的函數指針:

extern ngx_http_output_header_filter_pt ngx_http_top_header_filter; 
extern ngx_http_output_body_filter_pt   ngx_http_body_body_filter;

當執行ngx_http_send_header發送http頭部時,就開始從ngx_http_tou_header_filter指針遍歷所有的http頭部過濾模塊。

ngx_http_send_header:

ngx_int_t  
ngx_http_send_header(ngx_http_request_t *r)  
{  
    if (r->err_status) {  
        r->headers_out.status = r->err_status;  
        r->headers_out.status_line.len = 0;  
    }  
  
    return ngx_http_top_header_filter(r);//鏈表頭,指向第一個過濾頭部的模塊函數
}  

當執行ngx_http_output_filter發送包體時,就從ngx_http_top_body_filter指針遍歷所有的http包體過濾模塊。

ngx_http_output_filter:

ngx_int_t  
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)  
{  
    ngx_int_t          rc;
	ngx_connection_t  *c;

	c = r->connection;

	rc = ngx_http_top_body_filter(r, in);

	if (rc == NGX_ERROR) {
		c->error = 1;
	}

	return rc;//鏈表頭,指向第一個過濾包體的模塊函數
}

那麼nginx是怎樣用ngx_http_top_header_filter 和 ngx_http_top_body_filter把所有過濾頭部的模塊和所有過濾包體的模塊鏈接起來的呢?

2.3 過濾模塊的鏈接

原來每個過濾模塊都定義了兩個(這裏假設每個過濾模塊都處理頭部和包體)static的函數指針:

static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 
static ngx_http_output_body_filter_pt   ngx_http_next_body_filter;

ngx_http_next_header_filter來保存ngx_http_top_header_filter指向的處理模塊函數,而ngx_http_top_header_filter則指向本模塊處理頭部的過濾函數。

ngx_http_next_body_filter來保存ngx_http_top_body_filter指向的處理模塊函數,而ngx_http_top_body_filter則指向本模塊處理包體的過濾函數。

代碼表示如下:

ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter  = ngx_http_current_myfilter_header_filter;//處理頭部的過濾函數

ngx_http_next_body_filter   = ngx_http_top_body_filter;
ngx_http_top_body_filter    = ngx_http_current_myfilter_body_filter;//處理包體的過濾函數

這樣所有過濾模塊就被鏈接起來了。

比如現在有兩個過濾模塊filter_a, filter_b,filter_a模塊先定義,filter_b模塊後定義。

在filter_a 裏面有如下代碼:

ngx_http_next_header_filter_a = ngx_http_top_header_filter;
ngx_http_top_header_filter  = ngx_http_filtera_header_filter;

ngx_http_next_body_filter_a   = ngx_http_top_body_filter;
ngx_http_top_body_filter    = ngx_http_filtera_body_filter;

注意這時ngx_http_header_filter指向filter_a的頭部處理函數,ngx_http_top_body_filter指向filter_a的包體處理函數。

在filter_b裏面有如下代碼:

ngx_http_next_header_filter_b = ngx_http_top_header_filter;
ngx_http_top_header_filter  = ngx_http_filterb_header_filter;

ngx_http_next_body_filter_b   = ngx_http_top_body_filter;
ngx_http_top_body_filter    = ngx_http_filterb_body_filter;

這樣以後,ngx_http_next_header_filter_b保存的則是ngx_http_filtera_header_filter,即指向filtera中處理頭部的過濾函數,filtera和filterb的頭部過濾鏈表就建立起來了。

同樣,ngx_http_next_body_filter_b保存的是ngx_http_filterb_body_filter,即指向filtera中處理包體的過濾函數,filtera和filterb的包體過濾鏈表就建立起來了。

而此時ngx_http_top_header_filter保存的是filterb的頭部過濾函數,ngx_http_top_body_filter保存的是filterb的包體過濾函數。

我們實現filterb的頭部過濾函數時的代碼往往如下:

ngx_int_t ngx_http_next_header_filter_b(ngx_http_request_r *r)
{
	//處理頭部的代碼
    ......
    return ngx_http_next_header_filter_b(r, in);//調用filtera處理頭部的函數
}

實現filterb的包體過濾函數時的代碼往往如下:

ngx_int_t ngx_http_next_body_filter_b(ngx_http_request_t *r, ngx_chain_t *in)
{
	//處理包體的代碼
	......
    return ngx_http_header_filter_b(r, in); //調用filtera處理包體的函數
}

呵呵,這樣模塊filtera和模塊filterb就串聯起來了,並且filterb的執行在filtera執行之前,這就解釋了2.1中模塊的執行爲什麼是倒序。

假如有模塊filterc、filterd......都是按照filtera和filterb鏈接的方式鏈接在兩個鏈表中的。

3實現一個簡單的過濾模塊

實現一個簡單的過濾模塊在頭部加入字符串"[my filter prefix] "。

3.1config的編寫:

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"

注意與handler模塊的區別:HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_myfilter_module"

剛開始我就在這裏跳進坑了,找了好久,原來不是handler模塊中寫的HTTP_MODULES。

3.2代碼編寫

其實實現挺簡單的分爲幾個步驟:

1)三個模塊結構體的實現:ngx_command_t 、ngx_http_moudle_t、ngx_moudle_t可以參考這篇文章

2)把當前處理頭部和包體的過濾函數鏈接如鏈表

3)分別實現過濾頭部的函數和過濾包體的函數

第一步 代碼:

typedef struct
{
	ngx_flag_t arg_flag;
}ngx_http_myfilter_loc_conf_t;

typedef struct
{
    ngx_int_t flag_count;
}ngx_http_myfilter_ctx_t;

static ngx_str_t filter_prefix = ngx_string("[my filter prefix]");
//static限定這兩個變量只在當前文件中有效
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 char* ngx_http_myfilter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void* ngx_http_myfilter_create_loc_conf(ngx_conf_t *cf);
static char* ngx_http_myfilter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_int_t ngx_http_myfilter_post_config(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);
           
static ngx_command_t ngx_http_myfilter_commands[] = 
{
    {
        ngx_string("add_prefix"),
        NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_http_myfilter_set,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_myfilter_loc_conf_t, arg_flag),
        NULL
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_myfilter_module_ctx = 
{
	NULL,
	ngx_http_myfilter_post_config,
	NULL,
	NULL,
	NULL,
	NULL,
	ngx_http_myfilter_create_loc_conf,
	ngx_http_myfilter_merge_loc_conf
};

ngx_module_t ngx_http_myfilter_module = 
{
	NGX_MODULE_V1,
	&ngx_http_myfilter_module_ctx,
	ngx_http_myfilter_commands,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};

第二步代碼:

static ngx_int_t ngx_http_myfilter_post_config(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;

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "post config is called!");
    
    return NGX_OK;
}

第三步代碼:

static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
	if (r->headers_out.status != NGX_HTTP_OK)
    {
		ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "headers_out.status = %d", r->headers_out.status);
		return ngx_http_next_header_filter(r);
	}

	ngx_http_myfilter_loc_conf_t *mlcf;
	mlcf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
	if (mlcf->arg_flag == 0) //flag = 0,表示不加前綴
	{
		ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "arg_flag = 0");
		return ngx_http_next_header_filter(r);
	}

	ngx_http_myfilter_ctx_t *mctx;
	mctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if (mctx != NULL) //mctx不爲空,表示這個頭部已經處理過,不許要再處理了
	{
		return ngx_http_next_header_filter(r);
	}
	
	mctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
	if (mctx == NULL)
	{
		return NGX_ERROR;
	}

	ngx_http_set_ctx(r, mctx, ngx_http_myfilter_module);
	mctx->flag_count = 0; //0:初始化爲不加前綴
	ngx_str_t type = ngx_string("text/plain");
	if (r->headers_out.content_type.len >= type.len
		&& ngx_strncasecmp(r->headers_out.content_type.data, type.data, type.len) == 0)
	{
		mctx->flag_count = 1; //1:頭部類型爲“text/plain”的要加前綴
		if (r->headers_out.content_length_n > 0)
		{
			r->headers_out.content_length_n += filter_prefix.len;
		}
	}

	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 *mctx;
	mctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if (mctx == NULL || mctx->flag_count != 1)
	{
		return ngx_http_next_body_filter(r, in);
	}

	mctx->flag_count = 2;
	ngx_buf_t* buf = ngx_create_temp_buf(r->pool, filter_prefix.len);
	buf->start = filter_prefix.data;
	buf->pos   = filter_prefix.data;
    buf->last  = filter_prefix.data + filter_prefix.len;

	ngx_chain_t* out = ngx_alloc_chain_link(r->pool);
	out->buf         = buf;
    out->next        = in;

    return ngx_http_next_body_filter(r, out);
}


http://blog.csdn.net/xiaoliangsky/article/details/39522357


完整代碼以後參考:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct
{
	ngx_flag_t arg_flag;
}ngx_http_myfilter_loc_conf_t;

typedef struct
{
    ngx_int_t flag_count;
}ngx_http_myfilter_ctx_t;

static ngx_str_t filter_prefix = ngx_string("[my filter prefix]");
//static限定這兩個變量只在當前文件中有效
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 char* ngx_http_myfilter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void* ngx_http_myfilter_create_loc_conf(ngx_conf_t *cf);
static char* ngx_http_myfilter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_int_t ngx_http_myfilter_post_config(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);
           
static ngx_command_t ngx_http_myfilter_commands[] = 
{
    {
        ngx_string("add_prefix"),
        NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_http_myfilter_set,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_myfilter_loc_conf_t, arg_flag),
        NULL
    },
    ngx_null_command
};

static ngx_http_module_t ngx_http_myfilter_module_ctx = 
{
	NULL,
	ngx_http_myfilter_post_config,
	NULL,
	NULL,
	NULL,
	NULL,
	ngx_http_myfilter_create_loc_conf,
	ngx_http_myfilter_merge_loc_conf
};

ngx_module_t ngx_http_myfilter_module = 
{
	NGX_MODULE_V1,
	&ngx_http_myfilter_module_ctx,
	ngx_http_myfilter_commands,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};


static ngx_int_t ngx_http_myfilter_post_config(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;

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "post config is called!");
    
    return NGX_OK;
}

static char* ngx_http_myfilter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	ngx_http_myfilter_loc_conf_t *mlcf = conf;
    char* rc = ngx_conf_set_flag_slot(cf, cmd, conf);
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "arg_flag = %d", mlcf->arg_flag);

	return rc;
}


static void* ngx_http_myfilter_create_loc_conf(ngx_conf_t *cf)
{
	ngx_http_myfilter_loc_conf_t *mlcf;
    mlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_myfilter_loc_conf_t));
	if (mlcf == NULL)
    {
		return NULL;
    }
    
    mlcf->arg_flag = NGX_CONF_UNSET;

    return mlcf;
}

static char* ngx_http_myfilter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
	ngx_http_myfilter_loc_conf_t* prev = parent;
	ngx_http_myfilter_loc_conf_t* conf = child;
	
	ngx_conf_merge_value(conf->arg_flag, prev->arg_flag, 0);

	return NGX_CONF_OK;
}

static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
	if (r->headers_out.status != NGX_HTTP_OK)
    {
		ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "headers_out.status = %d", r->headers_out.status);
		return ngx_http_next_header_filter(r);
	}

	ngx_http_myfilter_loc_conf_t *mlcf;
	mlcf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
	if (mlcf->arg_flag == 0) //flag = 0,表示不加前綴
	{
		ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "arg_flag = 0");
		return ngx_http_next_header_filter(r);
	}

	ngx_http_myfilter_ctx_t *mctx;
	mctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if (mctx != NULL) //mctx不爲空,表示這個頭部已經處理過,不許要再處理了
	{
		return ngx_http_next_header_filter(r);
	}
	
	mctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
	if (mctx == NULL)
	{
		return NGX_ERROR;
	}

	ngx_http_set_ctx(r, mctx, ngx_http_myfilter_module);
	mctx->flag_count = 0; //0:初始化爲不加前綴
	ngx_str_t type = ngx_string("text/plain");
	if (r->headers_out.content_type.len >= type.len
		&& ngx_strncasecmp(r->headers_out.content_type.data, type.data, type.len) == 0)
	{
		mctx->flag_count = 1; //1:頭部類型爲“text/plain”的要加前綴
		if (r->headers_out.content_length_n > 0)
		{
			r->headers_out.content_length_n += filter_prefix.len;
		}
	}

	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 *mctx;
	mctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	if (mctx == NULL || mctx->flag_count != 1)
	{
		return ngx_http_next_body_filter(r, in);
	}

	mctx->flag_count = 2;
	ngx_buf_t* buf = ngx_create_temp_buf(r->pool, filter_prefix.len);
	buf->start = filter_prefix.data;
	buf->pos   = filter_prefix.data;
    buf->last  = filter_prefix.data + filter_prefix.len;

	ngx_chain_t* out = ngx_alloc_chain_link(r->pool);
	out->buf         = buf;
    out->next        = in;

    return ngx_http_next_body_filter(r, out);
}


注意:因爲處理頭部的type類型爲"text/plain",所以應該訪問txt類型的文本纔有效。

在nginx/html文件裏面創建一個test.txt,內容爲:hhhhh。

然後在瀏覽器中輸入:localhost/test.txt

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章