您的位置:首页 > 运维架构 > Nginx

剖析Nginx的过滤模块功能

2015-01-12 16:10 239 查看
4.1.剖析头部过滤函数

头部过滤函数由下面三个基础部分组成:

1. 决定是否操纵这个回复

2. 操纵这个回复

3. 调用下一个过滤函数

举个例子,这里有一个简单版本的“没有修改过的”头部过滤函数。如果客户端的If-Modfied-Since头部与回复的Last-Modified头部匹配,就把状态设为 304 Not Modified。头部过滤函数只有一个参数ngx_http_request_t,但它可以让我们访问到客户端的头部和不久要发送的回复头部。

static

ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r) {

time_t if_modified_since;

if_modified_since = ngx_http_parse_time( r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len);

/* step 1: decide whether to operate */

if (if_modified_since != NGX_ERROR && if_modified_since == r->headers_out.last_modified_time) {

/* step 2: operate on the header */

r->headers_out.status = NGX_HTTP_NOT_MODIFIED;

r->headers_out.content_type.len = 0;

ngx_http_clear_content_length(r);

ngx_http_clear_accept_ranges(r);

}

/* step 3: call the next filter */

return ngx_http_next_header_filter(r);

}

headers_out结构体与处理模块中的一样( http/ngx_http_request.h ),也可以随便改。

4.2.剖析主体过滤函数
因为有了缓冲链表,主体过滤函数可能有点棘手,该函数每次只能操作一个缓 冲区(链表成员)。模块必须决定是否覆盖输入缓冲区,或者把缓冲去替换掉并分配一个新的缓冲,还是在原来的缓冲区后面插入一个新缓冲,这是个问题。复杂的时候,模块会接收到几个不完全的缓冲区,还需要继续操纵。不幸的是, Nginx不提供高层次的API来操作缓冲区链表,所以主体过滤函数可能很难懂(也很难写)。但是,这里有些你可以看到在实际中用到的操作。

一个主体filter的函数原型看起来这样:(源代码摘自Nginx的"chunked"过滤模块)

static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);

第一个参数是我们的老朋友请求结构体。第二个参数是指向当前链表头部的指针(可能有0、1或者多个缓冲)。

举个简单例子。假设我们像在每个请求的最后插入文本“<l!--Served by Nginx -->”。首先,我们需要确定回复的最后一个缓冲区是否包含在我们的缓冲区链表中。就像我说的那样,现在还没有一个完美的API,所以我们必须自己做循环:

ngx_chain_t *chain_link;

int chain_contains_last_buffer = 0;

for ( chain_link = in; chain_link->next != NULL; chain_link = chain_link->next ) {

if (chain_link->buf->last_buf)

chain_contains_last_buffer = 1;

}

如果没有找到最后的缓冲区,就返回:

if (!chain_contains_last_buffer) return ngx_http_next_body_filter(r, in);

很好,现在最后一个缓冲区已经存在链表中了。我们分配一个新缓冲区:

ngx_buf_t *b;

b = ngx_calloc_buf(r->pool);

if (b == NULL) {

return NGX_ERROR;

}

然后把数据放进去:

b->pos = (u_char *) "<!--Served by Nginx -->";

b->last = b->pos + sizeof("<!--Served by Nginx -->") -1;

把这个缓冲区挂到新的链表成员:

ngx_chain_t added_link;

added_link.buf = b;

added_link.next = NULL;

最后,把这个新的链表成员挂到先前链表的末尾:

chain_link->next = added_link;

然后根据变化重设 last_buf变量:

chain_link->buf->last_buf = 0;

added_link->buf->last_buf = 1;

返回修改过的链表,进入下一个输出过滤函数:

return ngx_http_next_body_filter(r, &in);

其实函数可以比我们做的多得多,比如,mod_perl ($response->body =~ s/$/ <!--Served by mod_perl -->/),缓冲区链表功能强大,可以让程序员在以此处理数据,以便客户端可以尽快获得数据。不管怎样,按照我的观点,Nginx实在应该有个干净的接口,程序员也可以摆脱链表中不一致的状态。到现在为止,我们操纵它是有风险的。

4.3过滤函数的注册
过滤模块通过在读入配置后调用的函数(postconfigration)来注册。它们一般包括两种过滤函数:头部过滤函(header filters)处理HTTP头,主体过滤函数(body filters)处理主体。它们在同一个位置注册。

以chunked filter模块为例,它的模块上下文是这样的:

static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {

NULL, /* preconfiguration */

ngx_http_chunked_filter_init, /* postconfiguration */

...

};

下面是ngx_http_chunked_filter_init函数做的一些事情:

static ngx_int_t

ngx_http_chunked_filter_init(ngx_conf_t *cf)

{

ngx_http_next_header_filter = ngx_http_top_header_filter;

ngx_http_top_header_filter = ngx_http_chunked_header_filter;

ngx_http_next_body_filter = ngx_http_top_body_filter;

ngx_http_top_body_filter = ngx_http_chunked_body_filter;

return NGX_OK;

}

这里到底做了些什么?如果你还记得,过滤模块组成了一条“接力链表”。当一个处理模块产生一个回复,它会调用两个函数:ngx_http_output_filter,调用全局函数ngx_http_top_body_filter;ngx_http_send_header,调用全局函数ngx_top_header_filter。

ngx_http_top_body_filter和ngx_http_top_header_filter是主体和头部各自的“链表头部”。在这个链上每个成员都有指向下一个成员的函数指针(这个指针称为ngx_http_next_body_filter和 ngx_http_next_header_filter)。当一个过滤模块执行完成后,它就调用下一个过滤模块,直到一个特殊的“写”过滤模块被调用,它把整个HTTP包裹起来。在 filter_init函数中,可以看到是把模块本身添加到过滤模块链中;它用next变量保存了一个指向旧的“top”过滤模块,然后声明自己是新的“top”过滤模块。(所以,最后注册进
过滤链的模块是最先被执行的。)

作者边注:这是如何正常工作的呢?

原来每个过滤函数用下面的语句或者错误代码返回:

return ngx_http_next_body_filter();

(注意:ngx_http_next_header_filter是局部全局变量,所以每个filter模块中该变量的值都不一样)所以,如果过滤链表达到最后一个成员(特别定义的),就会返回一个OK。如果出现错误,整条链表会被缩短,Nginx产生合适的错误消息。这是一条单向链表,仅仅使用函数引用,就可以实现快速的错误返回。聪明
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: