本文主要是写一个 Http Filter 模块。 本文不会详细的介绍如何写一个 Http Filter,以及其中的原理,想查看原理的可以自行网上搜索,或者参考书[1].本文最后有解决书[1]中过滤模块无作用的方法

处理 Http Filter 模块,主要是把所有的 Filter 串成一个链表,然后逐个处理,最后返回给用户。本文的 Filter 功能很简单,检测配置文件是否配置相关信息,如果配置了,那么用自己编译的 Filter 函数来处理。本文的全部代码可以从这里进行下载

下面是这个模块的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”

其中 ngx_http_myfilter_module 是模块名,ngx_http_myfilter_module.c 是模块代码。

首先我们需要在配置文件中设定一个配置项,来标记是否开启当前的过滤模块,我们使用 ngx_flag_t 变量来存储该变量

typedef struct{
ngx_flag_t enable //这个在配置文件里面的配置项,其结果将存储在 enable 中
}ngx_http_myfilter_conf_t

如果需要启用这个过滤模块,可以在配置文件中进行开启.而我们需要使用 ngx_http_myfilter_create_conf 函数和 ngx_http_myfilter_merge_conf 函数来设置 ngx_http_myfilter_conf_t.

static void ngx_http_myfilter_create_conf(ngx_conf_t cf)
{//
ngx_http_myfilter_conf_t mycfmycf = (ngx_http_myfilter_conf_t )ngx_pcalloc(cf->;pool, sizeof(ngx_http_myfilter_conf_t));
if(NULL == mycf)
return NULL
/ ngx_flag_t 类型的变量。如果使用预设函数 ngx_conf_set_flag_slot 解析配置项参数,那么必须初始化为 NGX_CONF_UNSET /
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
//这个函数的功能是,如果 conf->enable 设置了,就直接返回。
//如果没设置但是 prev->enable 设置了,那么就把 conf->enable 设置为 prev->enable
//否则设置为0
ngx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK;
}


下面定义 ngx_command_t 数组和 ngx_http_module_t 以及 ngx_module_t 从而定义整个过滤模块

static ngx_command_t ngx_http_myfilter_commands[] = {
{ngx_string(“add_prefix”), //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, //我们使用 Nginx 内置的 ngx_conf_set_flag_slot 来设置 enable[根据配置文件中 add_prefix 是否配置]
NGX_HTTP_LOC_CONF_OFFSET, //解析之后的值存在哪一块
offsetof(ngx_http_myfilter_conf_t, enable),//解析后的值具体存在哪,这里是存在 enable 中
NULL},
ngx_null_command
};
static ngx_http_module_t ngx_http_myfilter_module_ctx = {
NULL, //preconfiguration
ngx_http_myfilter_init, //postconfiguration 方法,把我们的过滤模块加入到过滤模块链中NULL,//create main
NULL,//init mainNULL,//create srv
NULL,//merge srvngx_http_myfilter_create_conf, //create loc
ngx_http_myfilter_merge_conf //merge loc
};

ngx_module_t ngx_http_myfilter_module = {
NGX_MODULE_V1,
ngx_http_myfilter_module_ctx, //模块上下文
ngx_http_myfilter_commands, //模块命令
NGX_HTTP_MODULE, //模块类型,过滤模块其实也是 HTTP 模块
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};


对于添加到过滤模块链中,Nginx 使用的是改变链表的头指针。以及 static 的局部指针,具体如下所示

//利用 static 的局部性,下面的两个变量只能在本文中使用,可以用来链接整个过滤链表
static ngx_http_output_header_filter_pt ngx_http_next_header_filter
static ngx_http_output_body_filter_pt ngx_http_next_body_filterstatic 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
//把当前的 body 过滤模块添加到整个链表中
ngx_http_next_body_filter = ngx_http_top_body_filter
ngx_http_top_body_filter = ngx_http_myfilter_body_filterreturn NGX_OK;
}


利用上面的函数把该过滤模块添加到整个链表中后,我们需要的就是写具体的 header_filter 和 body_filter。具体如下

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
//只对 NGX_HTTP_OK 进行过滤
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)
{/ 该请求上下文已经存在,说明这个过滤模块已经被调用过 1 次 */
return ngx_http_next_header_filter(r);
}

conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);

if(conf->enable == 0) //没有配置,或者配置为 off。直接跳过这个过滤模块
{
return ngx_http_next_header_filter(r);
}

ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));

if(NULL == ctx)
{
return NGX_ERROR
}

/ add_prefix 为 0 表示不加前缀 /
ctx->add_prefix = 0;

/ 将构造的上下文设置到当前请求中 /
ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);

/ 这里为什么需要在 sizeof 后面减1呢?这里还没太明白,我觉得是直接用 sizeof 的结果 /
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)
{
/ 设置为1表示需要在 HTTP 包体前加入前缀 */
ctx->add_prefix = 1;

if(r->headers_out.content_length_n > 0)
{
r->headers_out.content_length_n += filter_prefix.len;
}
}
return ngx_http_next_header_filter(r);
}
//对 body 进行过滤
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);

/ 如果获取不到上下文,或者上下文结构体中的 add_prefix 为0 或者2 时,都不会添加前缀,这时直接交给下一个 HTTP 过滤模块处理 /
if(ctx == NULL || ctx->add_prefix != 1)
{
return ngx_http_next_body_filter(r, in);
}

/ 将 add_prefix 设置为2, 这样即使 ngx_http_myfilter_body_filter 再次回调时,也不会重复添加前缀 /
ctx->add_prefix = 2;

ngx_buf_t b = ngx_create_temp_buf(r->pool, filter_prefix.len);
//filter_prefix 是我们定一个的一个 ngx_str_t 变量,存着我们将要添加的数据
b->start = b->pos = filter_prefix.data;
b->last = b->pos + filter_prefix.len;
//把我们的添加的数据加入到 ngx_chain_t 中
ngx_chain_t cl = ngx_alloc_chain_link(r->pool);
cl->buf = b;
cl->next = in;

return ngx_http_next_body_filter(r, cl);
}


到这里我们的过滤模块基本完成了,我们过滤的是 txt 文档,也就是说我们在 header_filter 中的判断,是否和 “text/plain” 一样,对于 “text/plain” 我们可以查看 /usr/local/nginx/conf/mime.types, 然后接下来我们可以在 /usr/local/nginx/html 下新建一个文件 123.txt 。然后我们通过请求 “127.0.0.1/123.txt”, 我们可以看到在文件内容的前面加上了相应的前缀。
Reference
1.《深入理解 Nginx》第6章

  1. nginx:将自己编写HTTP过滤模块融入nginx时遇到的问题

Comments