nginx http模块开发(8) - 响应过滤

2018-07-22 10:49:12

filter 模块是 nginx 里另一大类 http 模块,与 handler 不同的是,它不直接产生数据,而是对返回的响应头,响应头进行各种加工处理。比如增加响应头,修改响应体,gzip压缩等。

1.8.0之后也增加了对请求体的过滤。

// 请求体过滤函数原型
typedef ngx_int_t (*ngx_http_request_body_filter_pt)
    (ngx_http_request_t *r, ngx_chain_t *chain);

这里只阐述响应过滤。

// 响应头过滤函数原型
typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);

// 响应体过滤函数原型
typedef ngx_int_t (*ngx_http_output_body_filter_pt)
    (ngx_http_request_t *r, ngx_chain_t *chain);

filter 模块必须实现这两个函数以达到过滤的目的,但如果只处理响应头,则无需实现 ngx_http_output_body_filter_pt。

过滤函数列表

nginx 定义了过滤函数链表的头节点:

// 过滤链表头指针,过滤header
// 每个过滤模块都需要内部实现一个函数指针,链接为单向链表
// 在modules数组里位置在前的是链表末尾,后面的是链表前面
// 链表的最后一个模块是ngx_http_header_filter_module
ngx_http_output_header_filter_pt  ngx_http_top_header_filter;

// 过滤链表头指针,过滤body
// 每个过滤模块都需要内部实现一个函数指针,链接为单向链表
// 在modules数组里位置在前的是链表末尾,后面的是链表前面
// 链表的最后一个模块是ngx_http_write_filter_module
ngx_http_output_body_filter_pt    ngx_http_top_body_filter;

这两个函数指针都是全局可见的,每个 filter 模块都会在配置解析时把自己的过滤函数插入到链表头,然后内部又保留了下一个过滤模块的过滤函数,这样就会以链的方式逐个调用。

过滤模块的调用顺序跟在 configure 时生成的模块顺序是相反的

ngx_module_t *ngx_modules[] = {
    &ngx_http_write_filter_module,
    &ngx_http_header_filter_module,

    ...
};

ngx_http_write_filter_module 是第一个响应体过滤模块,那么它会是最后一个被执行。

ngx_http_header_filter_module 是第一个响应头过滤模块,它也会最后一个被执行。

接下来我们以响应头过滤模块为例阐述,假设 nginx 就只有三个响应头过滤模块,分别是

ngx_module_t *ngx_modules[] = {
    &ngx_http_write_filter_module
    &ngx_http_header_filter_module,
    &ngx_http_range_header_filter_module
    ...
};

过滤模块的注册都是在解析配置文件后 postconfiguration。我们先来看一下 ngx_http_write_filter_module 响应体过滤模块。

static ngx_http_module_t  ngx_http_write_filter_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_write_filter_init,    /* postconfiguration */
    ...
};

static ngx_int_t
ngx_http_write_filter_init(ngx_conf_t *cf)
{
    ngx_http_top_body_filter = ngx_http_write_filter;

    return NGX_OK;
}

ngx_int_t
ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
...
}

此时, ngx_http_top_body_filter 全局指针指向的是  ngx_http_write_filter_module 模块的  ngx_http_write_filter 函数。

接下来调用 ngx_http_header_filter_module 模块的 postconfiguration 。

static ngx_http_module_t  ngx_http_header_filter_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_header_filter_init,   /* postconfiguration */
    ...
};

static ngx_int_t
ngx_http_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_top_header_filter = ngx_http_header_filter;

    return NGX_OK;
}

static ngx_int_t
ngx_http_header_filter(ngx_http_request_t *r){
    ...
}

此时,ngx_http_top_header_filter 全局指针指向的是 ngx_http_header_filter_module 模块的 ngx_http_header_filter 函数。

接下来继续配置解析,解析到了 ngx_http_range_header_filter_module 模块,调用该模块的 postconfiguration。

//注意,这里必须声明为静态,只能在该文件内可见
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_http_module_t  ngx_http_range_body_filter_module_ctx = {
    NULL,                              /* preconfiguration */
    ngx_http_range_body_filter_init,   /* postconfiguration */
    ...
};

static ngx_int_t
ngx_http_range_body_filter_init(ngx_conf_t *cf){
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_range_body_filter;

    return NGX_OK;
}

static ngx_int_t
ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ...
    return ngx_http_next_body_filter(r, out);
}

看到没有,当解析配置到该模块时,先把上一个模块的全局指针 ngx_http_top_body_filter 指向的内容保存到 ngx_http_next_body_filter,然后让 ngx_http_top_body_filter 指向本模块的函数  ngx_http_range_body_filter(),那么到当 nginx 调用 ngx_http_top_body_filter() 函数的时候实际上是调用 ngx_http_range_body_filter()函数,然后再由该函数调用 ngx_http_next_body_filter(r, out) 来调用上一个模块 ngx_http_write_filter_module 的过滤函数  ngx_http_write_filter。

这样就实现了链式调用,顺序由下往上。响应头跟响应体原理一样。比如后面的响应头模块也可以这样写。

static ngx_int_t
ngx_http_range_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_range_header_filter;

    return NGX_OK;
}

过滤链表的运行机制

当 handler 模块生成响应内容,调用函数 ngx_http_send_header() 发送响应头,就会执行响应头过滤链表。

ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
    if (r->post_action) {
        return NGX_OK;
    }

    // 检查是否已经发送过,会出个alert级别的错误,但其实无必要
    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;
    }

    // 走过整个header过滤链表
    return ngx_http_top_header_filter(r);
}

响应体的过滤链表执行基本类似。

ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ...

    // 发送响应体,走过整个body过滤链表
    // 最后由ngx_http_write_filter真正的向客户端发送数据,调用send_chain
    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;
}


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/d1


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。