文章目录
- 过滤模块简介
- 执行时间和内容
- 执行顺序
- Nginx是怎么按照次序依次来执行各个过滤模块的呢
- 这些过滤模块的简述(按执行顺序)
- 模块编译
- 过滤模块分析
-
- 响应头过滤函数
- 响应体过滤函数
-
过滤模块简介
执行时间和内容
执行顺序
- filter的调用顺序在编译的时候就决定了,在auto/modules文件中有对其控制编译的脚本。
- 当编译完成后在objs/目录下的ngx_modules.c文件中有一个ngx_module_t类型的数组ngx_modules,里面记录着nginx所有使用到的模块:我们只关心filter模块
- 从write_filter到not_modified_filter,模块的执行顺序是反向的。也就是说最早执行的是not_modified_filter,然后各个模块依次执行。
- 所有第三方的模块只能加入到copy_filter和headers_filter模块之间执行。
Nginx是怎么按照次序依次来执行各个过滤模块的呢
通过局部的全局变量,比如,在每个filter模块,会有类似如下代码:
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_example_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_example_body_filter;
- ngx_http_top_header_filter是一个全局变量。当编译进一个filter模块的时候,就被赋值为当前filter模块的处理函数。
- 而ngx_http_next_header_filter是一个局部全局变量,它保存了编译前上一个filter模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。
- 每个模块想执行下一个过滤函数,只要调用一下ngx_http_next_header_filter这个局部变量。而整个过滤模块链的入口,需要调用ngx_http_top_header_filter这个全局变量。ngx_http_top_body_filter的行为与header fitler类似。
这些过滤模块的简述(按执行顺序)
filter模块 | 描述 |
---|
ngx_http_not_modified_filter_module | 默认打开,如果请求的if-modified-since等于回复的last-modified间值,说明回复没有变化,清空所有回复的内容,返回304 |
ngx_http_range_body_filter_module | 默认打开,只是响应体过滤函数,支持range功能,如果请求包含range请求,那就只发送range请求的一段内容 |
ngx_http_copy_filter_module | 始终打开,只是响应体过滤函数, 主要工作是把文件中内容读到内存中,以便进行处理 |
ngx_http_headers_filter_module | 始终打开,可以设置expire和Cache-control头,可以添加任意名称的头 |
ngx_http_userid_filter_module | 默认关闭,可以添加统计用来识别用户的cookie |
ngx_http_charset_filter_module | 默认关闭,可以添加charset,也可以将内容从一种字符集转换到另外一种字符集,不支持多字节字符集 |
ngx_http_ssi_filter_module | 默认关闭,过滤SSI请求,可以发起子请求,去获取include进来的文件 |
ngx_http_postpone_filter_module | 始终打开,用来将子请求和主请求的输出链合并 |
ngx_http_gzip_filter_module | 默认关闭,支持流式的压缩内容 |
ngx_http_range_header_filter_module | 默认打开,只是响应头过滤函数,用来解析range头,并产生range响应的头 |
ngx_http_chunked_filter_module | 默认打开,对于HTTP/1.1和缺少content-length的回复自动打开 |
ngx_http_header_filter_module | 始终打开,用来将所有header组成一个完整的HTTP头 |
ngx_http_write_filter_module | 始终打开,将输出链拷贝到r->out中,然后输出内容 |
模块编译
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是该模块的源代码。
过滤模块分析
相关结构体
-
单向链表: ngx_chain_t
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
在过滤模块中,所有输出的内容都是通过一条单向链表所组成。这种单向链表的设计,正好应和了Nginx流式的输出模式。每次Nginx都是读到一部分的内容,就放到链表,然后输出出去。这种设计的好处是简单,非阻塞,但是相应的问题就是跨链表的内容操作非常麻烦,如果需要跨链表,很多时候都只能缓存链表的内容。
-
单链表负载的就是ngx_buf_t结构体
typedef struct ngx_buf_s ngx_buf_t;
struct ngx_buf_s {
u_char *pos;
u_char *last;
off_t file_pos;
off_t file_last;
u_char *start;
u_char *end;
ngx_buf_tag_t tag;
ngx_file_t *file;
ngx_buf_t *shadow;
unsigned temporary:1;
unsigned memory:1;
unsigned mmap:1;
unsigned recycled:1;
unsigned in_file:1;
unsigned flush:1;
unsigned sync:1;
unsigned last_buf:1;
unsigned last_in_chain:1;
unsigned last_shadow:1;
unsigned temp_file:1;
int num;
};
一般buffer结构体可以表示一块内存,内存的起始和结束地址分别用start和end表示,pos和last表示实际的内容。如果内容已经处理过了,pos的位置就可以往后移动。如果读取到新的内容,last的位置就会往后移动。所以buffer可以在多次调用过程中使用。如果last等于end,就说明这块内存已经用完了。如果pos等于last,说明内存已经处理完了。
响应头过滤函数
- 响应头过滤函数主要的用处就是处理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_AGAIN,分别表示处理成功,失败和未完成。
可以把HTTP响应头的存储方式想象成一个hash表,在Nginx内部可以查找和修改各个响应头,ngx_http_header_filter_module过滤模块把所有的HTTP头组合成一个完整的buffer,最终ngx_http_write_filter_module过滤模块把buffer输出。
响应体过滤函数
- 响应体过滤函数是过滤响应主体的函数。ngx_http_top_body_filter这个函数每个请求可能会被执行多次,它的入口函数是ngx_http_output_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;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args);
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
c->error = 1;
}
return rc;
}
ngx_http_output_filter可以被一般的静态处理模块调用,也可能在upstream模块里面被调用,对于整个请求的处理阶段来说,他们的用处都是一样的,就是把响应内容过滤,然后发给客户端。
具体模块的响应体过滤函数的格式类似这样:
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一般不会分配过大的内存,处理的原则是收到一定的数据,就发送出去。一个简单的例子,可以看看Nginx的chunked_filter模块,在没有contentlengt的情况下,chunk模块可以流式(stream)的加上长度,方便浏览器接收和显示内容。
-
在响应体过滤模块中,尤其要注意的是buf的标志位,完整描述可以在“相关结构体”这个节中看到。如果buf中包含last标志,
说明是最后一块buf,可以直接输出并结束请求了。如果有flush标志,说明这块buf需要马上输出,不能缓存。如果整块
buffer经过处理完以后,没有数据了,你可以把buffer的sync标志置上,表示只是同步的用处。
-
当所有的过滤模块都处理完毕时,在最后的write_fitler模块中,Nginx会将in输出链拷贝到r->out输出链的末尾,然后调用sendfile或者writev接口输出。由于Nginx是非阻塞的socket接口,写操作并不一定会成功,可能会有部分数据还残存在r->out。在下次的调用中,Nginx会继续尝试发送,直至成功。
发出子请求
- Nginx过滤模块一大特色就是可以发出子请求,也就是在过滤响应内容的时候,你可以发送新的请求,Nginx会根据你调用的
先后顺序,将多个回复的内容拼接成正常的响应主体。一个简单的例子可以参考addition模块。 - Nginx是如何保证父请求和子请求的顺序呢? 当Nginx发出子请求时,就会调用ngx_http_subrequest函数,将子请求插入父请求的r->postponed链表中。子请求会在主请求执行完毕时获得依次调用。子请求同样会有一个请求所有的生存期和处理过程,也会进入过滤模块流程。
- 关键点是在postpone_filter模块中,它会拼接主请求和子请求的响应内容。r->postponed按次序保存有父请求和子请求,它是一个链表,如果前面一个请求未完成,那后一个请求内容就不会输出。当前一个请求完成时并输出时,后一个请求才可输出,当所有的子请求都完成时,所有的响应内容也就输出完毕了。
优化措施
- Nginx过滤模块涉及到的结构体,主要就是chain和buf,非常简单。在日常的过滤模块中,这两类结构使用非常频繁,Nginx采用类似freelist重复利用的原则,将使用完毕的chain或者buf结构体,放置到一个固定的空闲链表里,以待下次使用。
- 比如,在通用内存池结构体中,pool->chain变量里面就保存着释放的chain。而一般的buf结构体,没有模块间公用的空闲链表池,都是保存在各模块的缓存空闲链表池里面。对于buf结构体,还有一种busy链表,表示该链表中的buf都处于输出状态,如果buf输出完毕,这些buf就可以释放并重复利用了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)