1. HTTP过滤模块的地位, 作用与一般的HTTP模块是不同的, 一个请求可以被任意个HTTP过滤模块处理. 它所做的工作是对发送给用户的HTTP响应包做一些加工.
相较于"普通的HTTP模块更倾向于完成请求的核心功能, 比如static模块负责静态文件的处", HTTP过滤模块则处理一些附加的功能, 比如gzip过滤模块可以把发送给客户的静态文件进行压缩后处理再发出去; image_filter属于第三方过滤模块, 可以将图片的静态文件制作成缩略图.
而且, 许多过滤模块是可以根据需求叠加的, 可以经由多个过滤模块顺序处理最终发送给客户.
2. HTTP过滤模块只处理从服务器发送给客户端的HTTP响应, 而不处理客户端发往服务器的请求
3. 因为HTTP响应可以分为两部分, HTTP头部和HTTP包体. 所以, 对应的HTTP过滤模块可以选择性的只处理两者之一, 或者两者都处理
Nginx采用了比较特殊的方法来将各个模块的顺序固定. 即通过局部的全局变量和全局变量来控制. 比如我们可以在某个模块文件内部看到:
所有HTTP过滤模块都要实现过滤响应头和过滤包体(或者两者之一)的方法, 因此, 这个单向链表是围绕着每个文件(每个HTTP过滤模块)中的这两个方法来建立的. 所以每个元素就都是一个处理方法.
整个链表的入口被如下定义:
下面是ngx_http_send_header函数的实现:
过滤模块的执行顺序
HTTP过滤模块之间的调用顺序是十分重要的. 这里不细谈,
Nginx中模块的顺序是死的, 在执行./configure后产生的ngx_modules.c文件中已经定义好了, 也就是说模块的调用顺序是由configure生成的
如果对于模块执行的顺序不满意, 完全可以在configure命令执行后, make命令编译前, 修改ngx_modules.c中的内容, 对ngx_modules数组中的成员顺序进行调整
以下是<<深入理解Nginx>>中过滤模块开发的简单例子
因为同样是模块开发, 现在开发的过滤模块可能与之前的模块开发例子相类似.
首先是config文件内容:
于是建立一个结构体用于存储此配置项的信息:
现在有了存储信息的结构体, 下面就开始定义这个模块
接下来实现其中的函数, 要注意的是, 别忘了实现过滤模块最重要的包头, 包体过滤函数...
首先实现过滤模块的初始化方法, 该方法是把本模块中处理HTTP头部的ngx_http_output_header_filter_pt方法与处理包体的方法插入到过滤模块链表的首部
接着实现包头过滤函数:
在此之前, 我们需要一个上下文来帮助我们处理, 前面文章提到过上下文的作用, 这里不再解释.
在这个模块中需要使用上下文, 是因为HTTP头部过滤方法在1个请求中一般只被调用一次, 而包体处理方法在1个请求中是有可能被多次调用的. 但此过滤模块的目的是在包体头部处理, 所以我们利用上下文结构体来保存我们的处理信息.
0: 表示不需要在返回的响应包体前加前缀
1: 表示应当在包体前加前缀
2: 表示已经加过前缀了
下面就是包头过滤函数了:

处理完了包头, 继续实现处理包体的函数

到这里, 一个过滤模块就配置完了.
一个要注意的地方是, 这个模块需要提供一个ngx_str_t类型的变量, 这是用于在原有的响应包体前面加上的字符换内容, 比如这样:
相较于"普通的HTTP模块更倾向于完成请求的核心功能, 比如static模块负责静态文件的处", HTTP过滤模块则处理一些附加的功能, 比如gzip过滤模块可以把发送给客户的静态文件进行压缩后处理再发出去; image_filter属于第三方过滤模块, 可以将图片的静态文件制作成缩略图.
而且, 许多过滤模块是可以根据需求叠加的, 可以经由多个过滤模块顺序处理最终发送给客户.
2. HTTP过滤模块只处理从服务器发送给客户端的HTTP响应, 而不处理客户端发往服务器的请求
3. 因为HTTP响应可以分为两部分, HTTP头部和HTTP包体. 所以, 对应的HTTP过滤模块可以选择性的只处理两者之一, 或者两者都处理
过滤模块方法链表
既然我们知道了众多过滤模块会被组织到一起从而依次处理某一个请求, 接下来要了解的就是他们是如何被组织到一起, 以及调用顺序是如何的.Nginx采用了比较特殊的方法来将各个模块的顺序固定. 即通过局部的全局变量和全局变量来控制. 比如我们可以在某个模块文件内部看到:
//ngx_http_output_header_filter_pt类型的函数仅接收一个参数, 即请求对象.
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
//ngx_http_output_body_filter_pt则接收两个参数, 请求对象和chain. chain是要发送的HTTP包体
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_example_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_example_body_filter;
大致意思即将该模块插入到链表的头部.所有HTTP过滤模块都要实现过滤响应头和过滤包体(或者两者之一)的方法, 因此, 这个单向链表是围绕着每个文件(每个HTTP过滤模块)中的这两个方法来建立的. 所以每个元素就都是一个处理方法.
整个链表的入口被如下定义:
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
当执行ngx_http_send_header发送HTTP头部时, 就从ngx_http_top_header_filter指针开始遍历所有的HTTP过滤模块. 在执行ngx_http_output_filter发送包体时类似.下面是ngx_http_send_header函数的实现:
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->post_action) {
return NGX_OK;
}
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
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_next_header_filter和ngx_http_next_body_filter使得遍历单向链表成为了可能.过滤模块的执行顺序
HTTP过滤模块之间的调用顺序是十分重要的. 这里不细谈,
Nginx中模块的顺序是死的, 在执行./configure后产生的ngx_modules.c文件中已经定义好了, 也就是说模块的调用顺序是由configure生成的
如果对于模块执行的顺序不满意, 完全可以在configure命令执行后, make命令编译前, 修改ngx_modules.c中的内容, 对ngx_modules数组中的成员顺序进行调整
以下是<<深入理解Nginx>>中过滤模块开发的简单例子
这个例子的场景是:
用户请求由static静态文件模块进行了处理, 它会根据URI返回磁盘中的文件给用户. 而我们开发的过滤模块就会在返回给用户的响应包体前加一段字符串
我们可以在配置文件中自行选择是否开启此功能.因为同样是模块开发, 现在开发的过滤模块可能与之前的模块开发例子相类似.
首先是config文件内容:
ngx_addon_name=ngx_http_ben_filter_module
HTTP_FILTER_MODULES="HTTP_FILTER_MODULES ngx_http_ben_filter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_ben_filter_module.c"
接下来处理配置项
在配置文件中, 我们会有一个此模块的开关.(即选择是否使用此过滤模块, 要使用则参数为"on", 否则为"off")于是建立一个结构体用于存储此配置项的信息:
typedef struct{
ngx_flag_t enable;
}ngx_http_ben_filter_conf_t;
既然有了存储配置项信息的结构体, 趁热打铁把创建该结构体的函数贴出来:static void *ngx_http_ben_filter_create_conf(ngx_conf_t *cf){
ngx_http_ben_filter_conf_t *conf;
conf = (ngx_http_ben_filter_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_ben_filter_conf_t));
if(conf == NULL)
return NULL;
conf->enable = NGX_CONF_UNSET;
return conf;
}
前面提到过配置项的合并, 现在我们也允许该配置项出现在http{..}, server{..}中, 所以提供merge函数:static char *ngx_http_ben_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child){
ngx_http_ben_filter_conf_t *prev = (ngx_http_ben_filter_conf_t *)parent;
ngx_http_ben_filter_conf_t *conf = (ngx_http_ben_filter_conf_t *)child;
//使用Nginx提供的整型类型的合并函数. 如果没有地方提供这个配置项, 那么参数默认为0, 即默认关闭此过滤模块
ngx_conf_merge_value(conf->enable, prev->enable, 0);
return NGX_CONF_OK;
}
现在有了存储信息的结构体, 下面就开始定义这个模块
static ngx_command_t ngx_http_ben_filter_commands[] = {
{
ngx_string("ben_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_ben_filter_conf_t, enable),
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_ben_filter_module_ctx = {
NULL,
ngx_http_ben_filter_init,
NULL,
NULL,
NULL,
NULL,
ngx_http_ben_filter_create_conf,
ngx_http_ben_filter_merge_conf
};
ngx_module_t ngx_http_ben_filter_module{
NGX_MODULE_V1,
&ngx_http_ben_filter_module_ctx,
ngx_http_ben_filter_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
接下来实现其中的函数, 要注意的是, 别忘了实现过滤模块最重要的包头, 包体过滤函数...
首先实现过滤模块的初始化方法, 该方法是把本模块中处理HTTP头部的ngx_http_output_header_filter_pt方法与处理包体的方法插入到过滤模块链表的首部
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_ben_filter_init(ngx_conf_t *cf){
//将包头过滤函数插在链表头部
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_ben_filter_header_filter;
//将包体过滤函数插在链表头部
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_ben_filter_body_filter;
return NGX_OK;
}
接着实现包头过滤函数:
在此之前, 我们需要一个上下文来帮助我们处理, 前面文章提到过上下文的作用, 这里不再解释.
在这个模块中需要使用上下文, 是因为HTTP头部过滤方法在1个请求中一般只被调用一次, 而包体处理方法在1个请求中是有可能被多次调用的. 但此过滤模块的目的是在包体头部处理, 所以我们利用上下文结构体来保存我们的处理信息.
typedef struct {
ngx_int_t ben_prefix;
}ngx_http_ben_filter_ctx_t;
该变量的值可以是:0: 表示不需要在返回的响应包体前加前缀
1: 表示应当在包体前加前缀
2: 表示已经加过前缀了
下面就是包头过滤函数了:
static ngx_int_t
ngx_http_ben_filter_header_filter(ngx_http_request_t *r){
ngx_http_ben_filter_ctx_t *ctx;
ngx_http_ben_filter_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_ben_filter_module);
//我们定义的函数中, 只有包头才会创建这个上下文结构体的对象, 既然存在了, 说明这个函数被执行过了, 直接跳到下一个过滤模块吧
if(ctx)
return ngx_http_next_header_filter(r);
//想要设置ctx的值, 必须首先考虑配置文件中是否打开这个过滤模块
conf = ngx_http_get_module_loc_conf(r, ngx_http_ben_filter_module);
//配置文件选择关闭此模块, 于是就交给下一个模块处理
if(conf->enable == 0)
return ngx_http_next_header_filter(r);
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ben_filter_ctx_t));
if(ctx == NULL)
return NGX_ERROR;
ctx->ben_prefix = 0; //先设置好, 下文继续处理, 因为我们想要处理的响应包类型只是text/plain类型的
ngx_http_set_ctx(r, ctx, ngx_http_ben_filter_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->ben_prefix = 1;
//当处理模块已经在content-length中写入了HTTP包体的长度时, 我们需要把这个模块添加在包体的数据长度也算进去
if(r->headers_out.content_length_n > 0)
r->headers_out.content_length_n += ben_filter_prefix.len;
}
return ngx_http_next_header_filter(r);
}
处理完了包头, 继续实现处理包体的函数
static ngx_int_t
ngx_http_ben_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in){
ngx_http_ben_filter_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_ben_filter_module);
if(ctx == NULL || ctx->ben_prefix != 1)
return ngx_http_next_body_filter(r, in);
ctx->ben_prefix = 2; //表示这个过滤模块的动作已经结束了, 以后此模块再次被调用也不会添加prefix了
ngx_buf_t *b = ngx_create_temp_buf(r->pool, ben_filter_prefix.len);
b->start = b->pos = ben_filter_prefix.data;
b->last = b->start + ben_filter_prefix.len;
ngx_chain_t *out;
out = ngx_alloc_chain_link(r->pool);
out->buf = b;
out->next = in;
return ngx_http_next_body_filter(r, out);
}
到这里, 一个过滤模块就配置完了.
一个要注意的地方是, 这个模块需要提供一个ngx_str_t类型的变量, 这是用于在原有的响应包体前面加上的字符换内容, 比如这样:
ngx_str_t ben_filter_prefix = ngx_string("what waht hwat ???");