Nginx过滤模块分为header filter和body filter两类,分别在响应头发送前和响应体写入前触发,按注册顺序链式调用;body filter可多次执行,需处理分片、内存/文件buffer及last_buf标志,不可阻塞且须防重复处理。

开发 Nginx 过滤模块(Filter Module)是深入定制响应内容的关键能力,适用于动态注入 HTML、重写 JSON、添加安全头、日志埋点等场景。它不依赖 Lua 或外部进程,直接在 Nginx 内存中处理输出链(`ngx_chain_t`),性能高、可控性强。
理解 Filter 模块的执行时机与层级
Nginx 的 filter 分为 header filter 和 body filter 两类,分别在响应头发送前、响应体写入前触发。二者按注册顺序链式调用,且 body filter 可被多次调用(因响应体可能分片传输)。关键点:
- header filter 仅执行一次,适合修改 status、添加/覆盖 header(如
Content-Security-Policy) - body filter 接收 `ngx_chain_t *in` 链表,每个 `ngx_buf_t` 可能是内存块或文件映射,需判断 `buf->in_file` 和 `buf->last_buf` 标志
- 不可阻塞:所有操作必须同步完成,不能 sleep、不能发起网络请求
- 避免重复处理:需用 `r->main_filter_ctx` 或自定义 request ctx 标记是否已处理过
编写一个简单的 body filter:自动注入 HTML 脚本
以在 HTML 响应末尾插入一段 JS 为例(如统计脚本),核心步骤如下:
- 在 module 的 `ngx_http_next_body_filter` 保存原始 body filter 链指针
- 实现自己的 body filter 函数,在检测到 `buf->last_buf == 1` 且内容为 text/html 时,分配新 buffer 拼接原始内容 + 注入脚本
- 用 `ngx_palloc()` 在 request pool 中分配内存,确保生命周期匹配;用 `ngx_chain_get_free_buf()` 复用空闲链节点
- 构造新 chain 并调用 `ngx_http_next_body_filter(r, out_chain)` 向下游传递
注意:若原始响应已压缩(gzip),该 filter 会失效——需确保在 gzip filter 之后 注册(通过 `ngx_http_top_body_filter = my_filter` 并在 `ngx_http_output_filter` 前置调用)或主动解压/重压(复杂度高,一般建议关闭 gzip 或用 sub_filter 替代简单场景)。
调试与上线注意事项
Filter 模块极易引发段错误或响应截断,调试需谨慎:
- 始终检查 `buf->pos` 与 `buf->last` 边界,避免越界读写;打印日志用 `ngx_log_debug()`,开启 `error_log /path/debug notice;`
- 用 `curl -i` 和 `tcpdump` 观察实际响应字节流,确认注入位置和长度是否正确
- 测试大响应体(>64KB)、分块传输(chunked)、带文件子请求(subrequest)等边界情况
- 上线前务必在非生产环境用 ab / wrk 压测,关注内存增长与 CPU 占用——不当的内存分配会快速耗尽 worker 进程内存池
替代方案对比:什么情况下不该写 filter?
并非所有内容改写都适合写 C filter:
- 简单字符串替换(如替换 URL):优先用内置
sub_filter指令,配置即用,安全稳定 - 需解析 JSON/XML 或正则匹配:用
ngx_http_lua_module更灵活,开发效率高,适合中小流量 - 涉及外部服务调用(如鉴权、AB 测试分流):应放在 rewrite 或 access 阶段,或用 proxy_pass 转发,避免阻塞 filter 链
- 仅修改 header:用 `add_header`、`more_set_headers`(headers-more)足够,无需动 C 层
真正需要自研 filter 的典型场景是:高频、低延迟、确定格式的二进制/文本流处理,且对性能和资源控制有极致要求。











