《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块

时间:2021-03-20 22:29:06

一、Nginx的HTTP过滤模块特征

  一个请求可以被任意个HTTP模块处理

  在普通HTTP模块处理请求完毕并调用ngx_http_send_header()发送HTTP头部或调用ngx_http_output_filter()发送HTTP包体时,才会由这两个方法一次调用所有的HTTP过滤模块来处理这个请求。HTTP过滤模块仅处理服务器发送到客户端的响应,而不处理客户端发往服务器的HTTP请求。

  多个过滤模块的顺序的形成以及Nginx自带的过滤模块请参考原书。

 

二、编写一个HTTP过滤模块

   以向返回给用户的文本格式响应包体前加一段字符串"[my filter prefix]"为例,展示如何编写一个HTTP过滤模块。源代码来自于《深入理解Nginx》。

1.config文件的编写

  与前几篇博文的HTTP模块不同,HTTP过滤模块需要HTTP_FILTER_MODULES一项以把所有过滤模块一同编译,因此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"

  进行configure时,--add-module=PATH是一样的。

 

2.编写模块基本内容:模块定义、配置项处理

  由于需要在nginx.conf中加入一项flag类型的add_fix来控制这个过滤模块的使用与否,与这个配置项处理相关的ngx_http_myfilter_create_conf()、ngx_http_myfilter_merge_conf()、ngx_http_mytest_commands[]需要对应地进行处理。

typedef struct {
ngx_flag_t enable;
} ngx_http_myfilter_conf_t;

typedef
struct {
ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t;

 

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf)
{
ngx_http_myfilter_conf_t
*mycf;
mycf
= (ngx_http_myfilter_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_myfilter_conf_t));
if(mycf == NULL) {
return NULL;
}
mycf
->enable = NGX_CONF_UNSET;
return mycf;
}
ngx_http_myfilter_create_conf()
《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
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_conf_merge_value(conf
->enable,prev->enable,0);
return NGX_CONF_OK;
}
ngx_http_myfilter_merge_conf
《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string(
"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,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_myfilter_conf_t,enable),
NULL },
ngx_null_command
};
ngx_http_mytest_commands[]

  这样之后才是模块的上下文和模块定义:

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
static ngx_http_module_t ngx_http_myfilter_module_ctx = {
NULL,
ngx_http_myfilter_init,
NULL,
NULL,

NULL,
NULL,
ngx_http_myfilter_create_conf,
ngx_http_myfilter_merge_conf
};
ngx_http_myfilter_module_ctx
《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
ngx_module_t ngx_http_myfilter_module = {
NGX_MODULE_V1,
&ngx_http_myfilter_module_ctx,
ngx_http_mytest_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
ngx_http_myfilter_module

  从模块上下文可以看出,过滤功能在模块完成配置项处理后开始,其初始化方法为ngx_myfilter_init()。

 

3.过滤功能实现

  初始化方法ngx_myfilter_init()的功能仅仅是把当前过滤模块插入Nginx所有过滤模块的链表中。

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
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)
{
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;
}
ngx_int_t ngx_http_myfilter_init()

 

  头部处理方法是为了确定返回的类型是否为text/plain。如果是,则包体处理方法需要添加前缀。这里把前缀硬编码至模块源码中。

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
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;

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) {
return ngx_http_next_header_filter(r);
}
conf
= ngx_http_get_module_loc_conf(r,ngx_http_myfilter_module);
if(conf->enable == 0)
{
return ngx_http_next_header_filter(r);
}
ctx
= ngx_pcalloc(r->pool,sizeof(ngx_http_myfilter_ctx_t));
if(ctx == NULL)
{
return NGX_ERROR;
}
ctx
->add_prefix = 0;

ngx_http_set_ctx(r,ctx,ngx_http_myfilter_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
->add_prefix = 1;
if(r->headers_out.content_length_n > 0) {
r
->headers_out.content_length_n += filter_prefix.len;
}
}
return ngx_http_myfilter_header_filter(r);
}
ngx_http_myfilter_header_filter()

 

  包体处理方法根据头部处理方法的结果来为包体添加前缀。

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块
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);
if(ctx==NULL||ctx->add_prefix != 1) {
return ngx_http_next_body_filter(r,in);
}

ctx
->add_prefix = 2;

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

ngx_chain_t
*c1 = ngx_alloc_chain_link(r->pool);
c1
->buf = b;
c1
->next = in;
return ngx_http_next_body_filter(r,c1);
}
ngx_http_myfilter_body_filter()

 

三、过滤模块测试

  根据原作者编写的nginx.conf

    server {
listen
8080;

location
/ {
root
/;
add_prefix on;
}
}

可以看出,需要在/目录下(系统根目录)添加一个或多个任意内容的文本文件来进行测试。我写了一个内容为test的文本文件test.txt。

  输入curl http://localhost:8080/test.txt,可以看到返回的内容是[my filter prefix]test。

《深入理解Nginx》阅读与实践(四):简单的HTTP过滤模块

  当然,如果你放在/的不是纯文本文件,而是html文件或者其他类型文件,是不会增加这个前缀的。

  另外,把on改成off,你会发现前缀不再出现,说明过滤模块功能已经关闭。

 

  p.s.此书的后续章节是源码分析,实践环节比较少,“《深入理解Nginx》阅读与实践”系列可能到此为止。