Nginx深入详解教程之过滤模块

发布时间:2019-08-12编辑:脚本学堂
本文介绍了nginx中过滤模块的相关知识,过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。

一、模块简介
过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。它的处理过程分为两个阶段,过滤HTTP回复的头部和主体,在这两个阶段可以分别对头部和主体进行修改。下面函数就是分别对头部和主体进行过滤的函数,所有模块的响应内容要返回给客户端,都必须调用这两个接口:
 

ngx_http_top_header_filter(r);
ngx_http_top_body_filter(r, in);

相关阅读:nginx图片过滤处理模块http_image_filter_module的配置详解

二、执行顺序
过滤模块的调用是有顺序的,这在编译的时候就决定了,控制编译的脚本位于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模块之间执行。每个filter模块的处理函数赋值给全局变量ngx_http_top_header_filter,而前一个filter模块的处理函数赋值给局部变量ngx_http_next_header_filter,响应头和响应体过滤函数的执行顺序如下图:

图1 header_filter和body_filter执行顺序图

三、模块编译
Nginx可以很方便地加入第三方过滤模块。在过滤模块的目录里加入config文件,内容如下:
 

复制代码 代码示例:
ngx_addon_name=ngx_http_example_filter_module
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_example_filter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_example_filter_module.c"

其中,ngx_http_example_filter_module为过滤模块名称,ngx_http_example_filter_module.c是该模块的源代码。

四、输出内容
基于Nginx流式输出模式,在过滤模块中,所有输出内容都是通过一条单向链表组成,每次Nginx都是读到一部分的内容就放到链表,然后输出出去。单向链表结构如下:
 

复制代码 代码示例:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
    ngx_buf_t *buf;
    ngx_chain_t *next;
};

一般buffer结构体可以表示一块内存,内存的起始和结束地址分别用start和end表示,pos和last表示实际的内容。如果内容已经处理过了,pos的位置就可以往后移动。如果读取到新的内容,last的位置就会往后移动。所以buffer可以在多次调用过程中使用。如果last等于end,就说明这块内存已经用完了。如果pos等于last,说明内存已经处理完了。下面是一个简单地示意图,说明buffer中指针的用法:

图2 buffer内存结构

五、过滤函数
1、响应头过滤函数
响应头过滤函数的主要用处是处理HTTP响应的头,可以根据实际情况对于响应头进行修改或者添加删除。响应头过滤函数先于响应体过滤函数,而且只调用一次,所以一般是做过滤模块的初始化工作,响应头过滤函数的入口如下:
 

复制代码 代码示例:
ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
{
    ...
    return ngx_http_top_header_filter(r);
}

该函数在向客户端发送回复的时候调用,返回值一般为NGX_OK、NGX_ERROR和NGX_AGIN,分别表示处理成功、失败和未完成。
ngx_http_header_filter_module过滤模块把所有的HTTP头组合成一个完成的buffer,最终由ngx_http_write_filter_module过滤模块把buffer输出。

2、响应体过滤函数
响应体过滤函数是过滤响应主体的函数。对于每个请求,函数ngx_http_top_body_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) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
    }

    return rc;
}

对于整个请求的处理阶段来说,ngx_http_output_filter的作用就是把响应内存过滤,然后发给客户端,具体模块的响应体过滤函数的格式会类似如下:
 

复制代码 代码示例:
static int ngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ...
    return ngx_http_next_body_filter(r, in);
}

该函数的返回值一般是NGX_OK、NGX_ERROR和NGX_AGAIN,分别表示处理成功、失败和未完成。
响应的主体内容就存于单链表in,链表一般不会太长,有时in参数可能为NULL。in中存有buf结构体中,对于静态文件,这个buf大小默认是32K;对于反向代理的应用,这个buf可能是4k或者8k。为了保持内存的低消耗,Nginx一般不会分配过大的内存,处理的原则是收到一定的数据,就发送出去。

在响应体过滤模块中,尤其要注意的是buf的标志位,具体可以参看图2。如果buf中包含last标志,说明是最后一块buf,可以直接输出并结束请求了。如果有flush标志,说明这块buf需要马上输出,不能缓存。如果整块buffer经过处理完以后,没有数据了,你可以把buffer的sync标志置上,表示只是同步的用处。

当所有的过滤模块都处理完毕时,在最后的write_fitler模块中,Nginx会将in输出链拷贝到r->out输出链的末尾,然后调用sendfile或者writev接口输出。由于Nginx是非阻塞的socket接口,写操作并不一定会成功,可能会有部分数据还残存在r->out。在下次的调用中,Nginx会继续尝试发送,直至成功。

六、子请求
Nginx过滤模块一大特色就是可以发出子请求,即在过滤响应内容的时候,你可以发送新的请求,Nginx会根据你调用的先后顺序,将多个回复的内容拼接成正常的响应主体。一个简单的例子可以参考addtion模块。当Nginx发出子请求时,就会调用ngx_http_subrequest函数,将子请求插入父请求的r->postponed链表中。子请求会在主请求执行完毕时获得依次调用。子请求同样会有一个请求所有的生存期和处理过程,也会进入过滤模块流程。

关键点是在postpone_filter模块中,它会拼接主请求和子请求的响应内容。r->postponed按次序保存有父请求和子请求,它是一个链表,如果前面一个请求未完成,那后一个请求内容就不会输出。当前一个请求完成时并输出时,后一个请求才可输出,当所有的子请求都完成时,所有的响应内容也就输出完毕了。