开发一个HTTP过滤模块的步骤和相关知识跟开发一个普通的HTTP模块是类似的,只不过HTTP过滤模块的地位、作用与正常的HTTP过滤模块不同,它的工作是对发送给用户的HTTP响应包做一些加工。
本文将学习开发一个简单的HTTP过滤模块,它能够对Content-Type为text/plain
的包体前加上前缀字符串prefix。
(一)过滤模块的调用顺序 |
过滤模块可以叠加,也就是说一个请求会被所有的HTTP过滤模块依次处理。
过滤模块的调用是有顺序的,它的顺序在编译的时候就决定了。控制编译的脚本位于auto/modules
中,当你编译完Nginx以后,可以在objs目录下面看到一个ngx_modules.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_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
,模块的执行顺序是反向的。也就是说最早执行的是not_modified_filter
,然后各个模块依次执行。所有第三方的模块只能加入到copy_filter
和headers_filter
模块之间执行。
在编译Nginx源码时,已经定义了一个由所有HTTP过滤模块组成的单链表,这个单链表是这样的:
链表的每一个元素都是一个C源代码文件,这个C源代码文件中有两个指针,分别指向下一个过滤模块(文件)的过滤头部和包体的方法(可理解为链表中的next指针)
过滤模块单链表示意图:
这两个指针的声明如下:
/*过滤模块处理HTTP头部的函数指针类型定义,它携带一个参数:请求*/
typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
/*过滤模块处理HTTP包体的函数指针类型定义,它携带两个参数:请求、要发送的包体*/
typedef ngx_int_t (*ngx_http_output_body_filter_pt)
(ngx_http_request_t *r, ngx_chain_t *chain);
在我们定义的第三方模块中则有如下声明:
/*用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;
那么怎么将这个源文件(节点),插入到HTTP过滤模块组成的单链表中去呢?
Nginx采用头插法的办法,所有的新节点都插入在链表的开头:
//插入到头部处理方法链表的首部
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;
其中两个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;
由于是头插法,这样就解释了,越早插入链表的过滤模块,就会越晚执行。
(二)开发一个简单的过滤模块 |
要开发一个简单的过滤模块,它的功能是对Content-Type
为text/plain
的响应添加一个前缀,类似于开发一个HTTP模块,它应该遵循如下步骤:
1.确定源代码文件名称,源代码所在目录创建
config
脚本文件,config
文件的编写方式跟HTTP模块开发基本一致,不同的是需要将HTTP_MODULES
改成HTTP_FILTER_MODULES
。2.定义过滤模块。实例化
ngx_module_t
类型模块结构,因为HTTP过滤模块也是HTTP模块,所以其中的type成员也是NGX_HTTP_MODULE
。3.处理感兴趣的配置项,通过设置
ngx_module_t
中的ngx_command_t
数组来处理感兴趣的配置项。4.实现初始化方法。初始化方法就是把本模块中处理HTTP头部的
ngx_http_output_header_filter_pt
方法和处理HTTP包体的ngx_http_output_body_filter_pt
方法插入到过滤模块链表的首部。5.实现4.中提到两个处理头部和包体的方法。
接下来按照上述步骤依次来实现:
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 定义过滤模块,实例化ngx_module_t
/*定义过滤模块,ngx_module_t结构体实例化*/
ngx_module_t ngx_http_myfilter_module =
{
NGX_MODULE_V1, /*Macro*/
&ngx_http_myfilter_module_ctx, /*module context*/
ngx_http_myfilter_commands,