nginx-源码带读-06-过滤链与模块开发
NGINX 源码带读 第6篇:过滤链与模块开发
本篇目标
理解NGINX的输出过滤链机制,并学习如何开发自定义HTTP模块。
前置知识
- 阅读过第1-5篇
- 了解HTTP响应格式
1. 过滤链(Filter Chain)
1.1 概念
NGINX使用过滤链(filter chain)处理HTTP响应。每个过滤器负责响应的一个方面,多个过滤器串联形成处理流水线。
1.2 过滤器类型
| 类型 | 说明 |
|---|---|
| Header Filter | 处理响应头(Content-Type、Content-Length等) |
| Body Filter | 处理响应体(gzip压缩、chunk编码等) |
1.3 核心过滤器
Header filter链(按顺序执行):
ngx_http_not_modified_filter 修改304响应
ngx_http_headers_filter 设置响应头
ngx_http_image_filter 图片处理(如有)
ngx_http_gzip_header_filter gzip头处理
ngx_http_write_filter 写入socket(链表末端)
Body filter链(按顺序执行):
ngx_http_gzip_body_filter gzip压缩
ngx_http_chunked_filter chunk编码
ngx_http_write_filter 写入socket(链表末端)
1.4 Header Filter链
// src/http/ngx_http_header_filter_module.c
ngx_http_output_header_filter_pt ngx_http_next_header_filter;
ngx_int_t ngx_http_header_filter(ngx_http_request_t *r) {
// 1. 检查是否需要发送响应头
if (r->header_only) {
return ngx_http_next_header_filter(r);
}
// 2. 构建响应头
// 状态行:HTTP/1.1 200 OK
// 头部字段:Content-Type, Content-Length等
// 3. 调用下一个过滤器
return ngx_http_next_header_filter(r);
}
1.5 Body Filter链
// src/http/ngx_http_write_filter_module.c
ngx_http_output_body_filter_pt ngx_http_next_body_filter;
ngx_int_t ngx_http_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {
ngx_chain_t *cl;
// 1. 遍历chain链表
for (cl = in; cl; cl = cl->next) {
// 2. 检查缓冲区
if (cl->buf->pos == cl->buf->last) {
continue; // 空缓冲区
}
// 3. 检查是否是最后一个缓冲区
if (cl->buf->last_buf) {
r->connection->buffered &= ~NGX_HTTP_LOWEL_BUFFERED;
}
}
// 4. 调用下一个过滤器
return ngx_http_next_body_filter(r, in);
}
1.6 ngx_http_write_filter —— 最末端过滤器
这是过滤链的末端,负责将响应数据写入socket:
// src/http/ngx_http_write_filter_module.c
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) {
ngx_connection_t *c;
ngx_event_t *wev;
ngx_chain_t *cl, *ln;
ngx_buf_t *b;
ssize_t n;
ngx_int_t rc;
c = r->connection;
wev = c->write;
// 1. 将chain追加到输出链表
if (in == NULL) {
return NGX_OK;
}
// 2. 遍历chain,计算总大小
size = 0;
for (cl = in; cl; cl = cl->next) {
b = cl->buf;
size += ngx_buf_size(b);
}
// 3. 如果有数据需要发送
if (size > 0) {
// 尝试直接发送
n = ngx_write_chain(c, in);
if (n == NGX_AGAIN) {
// 需要等待写事件
wev->handler = ngx_http_write_filter_handler;
ngx_add_timer(wev, r->send_timeout);
return NGX_AGAIN;
}
if (n == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_HTTP_SERVER_ERROR);
return NGX_ERROR;
}
// 4. 检查是否发送完成
if (cl->buf->last_buf) {
r->connection->buffered &= ~NGX_HTTP_LOWEL_BUFFERED;
}
}
return NGX_OK;
}
1.7 过滤链初始化
// src/http/ngx_http.c
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf) {
// 初始化header filter链
ngx_http_next_header_filter = ngx_http_header_filter;
// 初始化body filter链
ngx_http_next_body_filter = ngx_http_body_filter;
return NGX_OK;
}
2. 缓冲区和链表
2.1 ngx_buf_t
// src/core/ngx_buf.h
typedef struct ngx_buf_s {
u_char *pos; // 内存缓冲区起始位置
u_char *last; // 内存缓冲区结束位置
off_t file_pos; // 文件缓冲区起始偏移
off_t file_last; // 文件缓冲区结束偏移
ngx_buf_tag_t tag; // 缓冲区标记(用于过滤器识别)
ngx_output_chain_t *output_chain; // 输出链
ngx_buf_file_t file; // 文件对象
unsigned temporary:1; // 内存缓冲区(可修改)
unsigned memory:1; // 内存缓冲区(只读)
unsigned mmap:1; // mmap映射的内存
unsigned in_file:1; // 文件缓冲区
unsigned flush:1; // 刷新标记
unsigned sync:1; // 同步标记
unsigned last_buf:1; // 最后一个缓冲区(响应结束)
unsigned last_in_chain:1; // 链中最后一个
unsigned temp_file:1; // 临时文件
} ngx_buf_t;
2.2 缓冲区标志位说明
| 标志位 | 含义 |
|---|---|
temporary |
内存缓冲区,可修改,发送后可释放 |
memory |
内存缓冲区,只读,发送后不可释放 |
mmap |
mmap映射的内存,需要munmap |
in_file |
文件缓冲区,通过sendfile发送 |
flush |
刷新标记,强制发送 |
sync |
同步标记,等待发送完成 |
last_buf |
响应的最后一个缓冲区 |
last_in_chain |
当前chain的最后一个缓冲区 |
2.3 ngx_chain_t
// src/core/ngx_buf.h
struct ngx_chain_s {
ngx_buf_t *buf; // 缓冲区
ngx_chain_t *next; // 下一个链节点
};
链表是NGINX在各模块间传递数据的主要方式。
2.4 缓冲区创建
// 创建临时缓冲区
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size) {
ngx_buf_t *b;
b = ngx_calloc_buf(pool);
if (b == NULL) {
return NULL;
}
b->pos = ngx_palloc(pool, size);
if (b->pos == NULL) {
return NULL;
}
b->last = b->pos;
b->temporary = 1; // 临时内存缓冲区
return b;
}
// 创建文件缓冲区
ngx_buf_t *ngx_create_file_buf(ngx_pool_t *pool, ngx_fd_t fd,
off_t offset, size_t size) {
ngx_buf_t *b;
b = ngx_calloc_buf(pool);
if (b == NULL) {
return NULL;
}
b->in_file = 1;
b->file = ngx_pcalloc(pool, sizeof(ngx_file_t));
b->file->fd = fd;
b->file_pos = offset;
b->file_last = offset + size;
return b;
}
3. 自定义HTTP模块实战
3.1 模块结构
一个HTTP模块需要:
- 模块定义(ngx_module_t)
- 模块上下文(ngx_http_module_t)
- 配置结构体(存储模块配置)
- 配置命令(ngx_command_t)
- 处理函数(实际逻辑)
3.2 示例:返回当前时间的模块
// ngx_http_mytime_module.c
// 1. 配置结构体
typedef struct {
ngx_flag_t enable;
} ngx_http_mytime_loc_conf_t;
// 2. 内容处理函数
static ngx_int_t ngx_http_mytime_handler(ngx_http_request_t *r) {
// 设置响应头
ngx_str_set(&r->headers_out.content_type, "text/plain");
r->headers_out.status = NGX_HTTP_OK;
// 构建响应体
u_char *body = ngx_pnalloc(r->pool, 64);
ngx_sprintf(body, "Current time: %T\n", ngx_time());
// 发送响应
r->headers_out.content_length_n = ngx_strlen(body);
ngx_buf_t *b = ngx_create_temp_buf(r->pool, ngx_strlen(body));
b->pos = body;
b->last = body + ngx_strlen(body);
b->last_in_chain = 1;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
// 3. 配置命令
static ngx_command_t ngx_http_mytime_commands[] = {
{ ngx_string("mytime"),
NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_mytime_loc_conf_t, enable),
NULL },
ngx_null_command
};
// 4. 模块上下文
static ngx_http_module_t ngx_http_mytime_module_ctx = {
NULL, // preconfiguration
NULL, // postconfiguration
NULL, // create_main_conf
NULL, // init_main_conf
NULL, // create_srv_conf
NULL, // merge_srv_conf
ngx_http_mytime_create_loc_conf,
ngx_http_mytime_merge_loc_conf
};
// 5. 模块定义
ngx_module_t ngx_http_mytime_module = {
NGX_MODULE_V1,
&ngx_http_mytime_module_ctx,
ngx_http_mytime_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL,
NULL, NULL,
NULL,
NGX_MODULE_V1_PADDING
};
// 6. 创建loc_conf
static void *ngx_http_mytime_create_loc_conf(ngx_conf_t *cf) {
ngx_http_mytime_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mytime_loc_conf_t));
if (conf == NULL) {
return NULL;
}
conf->enable = NGX_CONF_UNSET;
return conf;
}
// 7. 合并loc_conf
static char *ngx_http_mytime_merge_loc_conf(ngx_conf_t *cf,
void *parent, void *child) {
ngx_http_mytime_loc_conf_t *prev = parent;
ngx_http_mytime_loc_conf_t *conf = child;
ngx_conf_merge_value(conf->enable, prev->enable, 0);
// 如果启用,注册handler
if (conf->enable) {
ngx_http_handler_pt *h;
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
h = ngx_array_push(&clcf->handlers);
if (h == NULL) {
return NGX_CONF_ERROR;
}
*h = ngx_http_mytime_handler;
}
return NGX_CONF_OK;
}
3.3 编译方式
# 静态编译
# 在objs/ngx_modules.c中添加模块
./configure --add-module=/path/to/module
make
# 动态编译
./configure --add-dynamic-module=/path/to/module
make modules
# 生成 objs/ngx_http_mymodule.so
3.4 使用
location /time {
mytime on;
}
4. 过滤链定制
4.1 在过滤链中插入自定义过滤器
// 在postconfiguration中:
static ngx_int_t ngx_http_my_filter_init(ngx_conf_t *cf) {
ngx_http_next_body_filter = ngx_http_body_filter;
ngx_http_body_filter = ngx_http_my_body_filter;
return NGX_OK;
}
这是经典的责任链模式——每个过滤器处理自己负责的部分,然后调用下一个。
4.2 过滤器执行顺序
请求到达
-> handler生成响应
-> ngx_http_output_filter(r, &out)
-> header_filter_1 -> header_filter_2 -> ... -> write
-> body_filter_1 -> body_filter_2 -> ... -> write
4.3 过滤器短路
某些过滤器可以短路处理:
ngx_int_t ngx_http_not_modified_filter(ngx_http_request_t *r) {
// 检查是否需要304响应
if (r->headers_in.if_modified_since != NULL) {
if (r->headers_out.last_modified_time <= if_modified_since) {
r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
r->headers_out.content_length_n = 0;
r->header_only = 1;
// 跳过body过滤器
return ngx_http_next_header_filter(r);
}
}
return ngx_http_next_header_filter(r);
}
5. 静态文件模块
5.1 文件发送流程
proxy_pass / static文件
-> ngx_http_static_module.c
-> ngx_http_static_handler()
-> open(file_path)
-> stat(file_path)
-> 创建in_file buf
-> ngx_http_output_filter(r, &out)
-> write_filter通过sendfile发送
5.2 sendfile优化
// src/os/unix/ngx_files.c
ssize_t ngx_write_chain(ngx_connection_t *c, ngx_chain_t *in) {
// 尝试使用sendfile
if (c->sendfile) {
n = ngx_write_chain_to_file(c, in, c->file, c->offset);
} else {
// 回退到writev
n = ngx_write_chain_to_socket(c, in);
}
return n;
}
6. 本篇小结
| 概念 | 要点 |
|---|---|
| 过滤链 | header filter + body filter,责任链模式 |
| 缓冲区 | ngx_buf_t,支持内存和文件 |
| 链表 | ngx_chain_t,模块间数据传递 |
| 模块开发 | 定义+上下文+配置+命令+处理函数 |
| 过滤器插入 | postconfiguration中链表替换 |
| write_filter | 最末端过滤器,实际发送数据 |
思考题
- 为什么NGINX用过滤链而不是直接在处理函数中构建响应?
- gzip过滤器应该插入过滤链的哪个位置?为什么?
- 如果要实现一个HTTP缓存模块,需要在哪几个阶段工作?
思考题解答
1. 为什么用过滤链而不是直接构建响应?
过滤链的优势:
-
单一职责原则:每个过滤器只负责一个功能(如gzip压缩、chunk编码、添加响应头),代码更清晰、更易维护。
-
可组合性:可以通过配置灵活组合过滤器,无需修改核心代码。例如:
gzip on; # 启用gzip过滤器 add_header X-Custom "value"; # 添加自定义头 -
可扩展性:新功能可以通过添加过滤器实现,不影响现有过滤器。例如:添加一个记录响应时间的过滤器。
-
性能优化:过滤器可以短路处理(如304响应不需要body过滤器),避免不必要的计算。
-
复用性:过滤器可以跨多个location复用,无需重复实现。
对比直接构建响应:
// 直接构建(不推荐)
ngx_int_t handler(ngx_http_request_t *r) {
// 1. 检查是否需要gzip
// 2. 检查是否需要chunk编码
// 3. 检查是否需要添加自定义头
// 4. 检查是否需要304处理
// 5. 构建响应
// 6. 发送响应
// 所有逻辑混在一起,难以维护
}
过滤链模式:
// 过滤链处理
handler -> header_filter -> chunk_filter -> gzip_filter -> write_filter
// 每个过滤器只关注自己的职责
2. gzip过滤器应该插入过滤链的哪个位置?
最佳位置:在header filter链的末尾之前,body filter链的中间。
原因:
-
header filter:gzip需要修改响应头(添加
Content-Encoding: gzip),所以在header filter链中工作。 -
body filter:gzip需要压缩响应体,所以在body filter链中处理。
-
位置选择:
- 在
ngx_http_not_modified_filter之后(避免压缩304响应) - 在
ngx_http_chunked_filter之前(压缩后编码) - 在
ngx_http_headers_filter之后(压缩前添加必要的头)
- 在
过滤链顺序:
Header filter链:
realip -> not_modified -> headers -> gzip_header -> ... -> write
Body filter链:
... -> gzip_body -> chunked -> write
配置影响:
gzip on;
gzip_types text/plain application/json;
gzip_min_length 1024; # 小于1024字节不压缩
gzip过滤器会在body filter链中检查这些配置,决定是否压缩。
3. 实现HTTP缓存模块需要在哪几个阶段工作?
需要工作的阶段:
-
PREACCESS阶段(阶段5):
- 检查缓存是否存在
- 如果缓存命中,直接返回缓存内容
- 实现:
ngx_http_cache_filter模块
-
CONTENT阶段(阶段9):
- 如果缓存未命中,代理到后端
- 将后端响应存入缓存
- 实现:
ngx_http_proxy_module中的缓存逻辑
-
LOG阶段(阶段10):
- 记录缓存命中/未命中状态
- 用于监控和统计
缓存处理流程:
请求到达
-> PREACCESS:检查缓存
-> 缓存命中:直接返回缓存内容
-> 缓存未命中:继续
-> ACCESS:权限检查
-> CONTENT:代理到后端
-> 收到响应
-> 存入缓存
-> 返回响应
缓存模块的关键组件:
typedef struct {
ngx_flag_t enable; // 是否启用缓存
ngx_str_t cache_path; // 缓存路径
ngx_uint_t cache_size; // 缓存大小
ngx_uint_t cache_ttl; // 缓存过期时间
ngx_http_cache_key_t key; // 缓存键
} ngx_http_cache_loc_conf_t;
缓存键的设计:
- 默认使用
$scheme$proxy_host$request_uri - 可以自定义:
proxy_cache_key $scheme$host$request_uri - 支持多个key的hash
缓存策略:
proxy_cache_valid 200 302 10m;:200/302响应缓存10分钟proxy_cache_valid 404 1m;:404响应缓存1分钟proxy_cache_use_stale error timeout updating;:在错误时使用过期缓存

浙公网安备 33010602011771号