nginx 的 filter 链仅处理响应,分为 header_filter 和 body_filter 两条静态单向链,按头插法注册、严格分阶段执行,header_filter 执行一次,body_filter 可多次调用,各模块须遵循事件驱动、内存池分配及非阻塞原则。

Nginx 的请求过滤(Filter)链并不处理客户端发来的请求,它只作用于服务器生成的响应——也就是“响应过滤”,而非“请求过滤”。这个常见误解需要先澄清:Nginx 中没有面向入站请求的通用 Filter 链;真正存在且被广泛使用的,是 HTTP 响应头(header_filter)和响应体(body_filter)的双链式处理机制,它们在响应即将发出前,按严格顺序对输出内容进行加工。
Filter 链不是动态调度,而是静态函数指针跳转
整个链由两个全局入口指针驱动:
- ngx_http_top_header_filter:header 链起点,所有 header 处理模块(如 charset、gzip header 添加)在此注册
- ngx_http_top_body_filter:body 链起点,负责响应体变换(如 gzip 压缩、range 截取、SSI 解析)
每个模块初始化时,会把当前链首地址存入自己的 static 变量(如 ngx_http_next_header_filter),再把自己的函数地址赋给对应全局指针。这样就形成一条“头插法”构建的单向链——越晚编译/注册的模块,越靠近链首,执行越早。调用时只是纯函数跳转,无事件调度、无队列、无上下文切换。
Header 和 Body 过滤严格分阶段、不可交叉
事件驱动决定了执行节奏:
- 当 upstream 返回响应后,Nginx 触发 output 阶段:先调用
ngx_http_send_header(),完整执行 header_filter 链,最终必须调用ngx_http_write_filter将头部写入连接缓冲区 - 只有 header 已发送(
r->header_sent == 1)后,才会进入 body 处理;ngx_http_output_filter()每次接收一批数据(可能只是部分响应体),触发 body_filter 链执行一次 - body_filter 可被反复调用,需识别
buf->last_buf判断是否为终帧;header_filter 则只执行一次,之后修改r->headers_out无效
每个 Filter 只做一件事:接收链表、返回链表
标准签名是:ngx_int_t filter(ngx_http_request_t *r, ngx_chain_t *in)
- 输入是
ngx_chain_t *,即缓冲区链表,每个节点含ngx_buf_t(内存或文件 buf) - 处理逻辑包括:遍历 buf、修改内容、拆分/合并 buffer、新增 buf(如注入脚本)、设置标志位(如
buf->last_buf) - 构造新 chain,最后调用保存的 next 函数(如
ngx_http_next_body_filter(r, out_chain))向下游传递 - 链尾始终是
ngx_http_write_filter,它是唯一真正把数据提交到 socket 写缓冲区的模块
开发 Filter 必须匹配事件生命周期
资源管理不能脱离请求上下文:
- 内存分配必须使用
r->pool(如ngx_palloc()),确保随请求自动释放,避免泄漏 - 跨多次 body_filter 调用的状态(如 HTML 标签解析进度),需存入
r->ctx或自定义 ctx 结构 - 禁止阻塞操作:不能 sleep、不能发起网络请求、不能同步读大文件——否则卡住整个事件循环,影响所有连接
- 注意执行顺序依赖:例如注入 HTML 脚本的 filter,必须在 gzip 之后(否则压缩后内容不可编辑),通常需手动调整模块注册顺序











