综合 nginx 谓词 location + client_body_early_read + json_set:按 payload 路由的真实代价

2026-10-24 21:05:16

nginx 1.31.5 落地笔记:谓词 location、提前读 body、原生 JSON 与 Control API

nginx 1.31.5(2026-09-02 mainline)的 CHANGES 里有四条 Feature:Control API(从 F5 NGINX Plus 开源)、predicate locations、ngx_http_json_module、client_body_early_read。后三条互相咬合,真正上线时要考虑的更多是代价,而不是「有没有」。

谓词 location:路由不再只按 URI

location 里可以直接写变量:

map $http_user_agent $is_ai_scraper {
    default                0;
    "~*(gptbot|claudebot)" 1;
}

server {
    location $is_ai_scraper {
        return 403;
    }
}

变量来源可以是 L4 网段、客户端证书、L7 请求头或自定义业务逻辑。好处是路由条件从「URI 字符串」扩展到「请求属性」,坏处是求值时机必须自己确认:哪些变量在 location 匹配阶段已经可用,哪些要等 rewrite/access 阶段才填充。用错阶段的变量,谓词会稳定地取到空值,而且不报错。

client_body_early_read:把 body 读在匹配之前

默认流程是先读请求头、选出 location、再缓冲请求体。开启后强制在 location 匹配之前完成请求体的缓冲与解析。

代价有三块:

  1. 内存。提前缓冲的 body 占用 client_body_buffer_size;超过部分按 nginx 常规行为写入 client_body_temp_path。
  2. 大文件与流式请求。上传、streaming、SSE 之类本来不需要在匹配前拿到 body 的请求,现在会先落盘再继续,磁盘 IO 与延迟都会上来。
  3. 限额保护。请求体在 location 匹配前就被读,如果 client_max_body_size 只在某个 location 里收紧,这个限制就落在读之后——前置读取阶段没有对应的 location 级保护。要么在更外层统一收紧,要么接受这段窗口。
server {
    client_body_early_read on;
    client_body_buffer_size 64k;
    client_max_body_size 1m;
}

json_set:只能解析已缓冲的 body

原生 JSON parser 把已缓冲 payload 的字段提取成 nginx 变量:

json_set $json_method $request_body "method";

因为作用对象是「已缓冲」的 body,这个模块实际上必须配 client_body_early_read。配上之后,JSON 变量在 location 求值前就已填充,于是可以直接当谓词用:

location $json_method {
    # 按 payload 里的 method 分流
}

提取出的变量也能喂给 map,做边缘校验或限流键。这套能力以前要靠 njs(如 MCP Observability Module)或 Lua 实现,现在是核心模块。

组合起来适合什么

/mcp、/api/v1、/graphql 这类同一 URI 上跑不同操作的 API 与 agent 流量:按 payload 路由、按 payload 做边缘校验,都不用再引入脚本层。前提是请求体小、结构稳定,并且能接受「匹配前先读 body」的成本。

上线前验证清单

  • nginx -t 过配置,同时确认谓词里用到的变量在该阶段确实有值(打日志验证,不要靠推测)。
  • 用 curl 构造同 URI 不同 payload 的请求,对比分流结果;用 ab 或类似工具压一轮,看开启 early_read 前后的延迟与错误率。
  • 观察 client_body_temp_path 下的临时文件增长,以及 worker 进程内存,确认 client_body_buffer_size / client_max_body_size 的组合处在可接受区间。

1.31.5 其他改动

FastCGI/uWSGI 支持多字节参数名长度;memcached 尊重最大长度结束标记;QUIC 拒绝 1-RTT 包中的 CRYPTO 帧;slice filter 增加溢出保护;event pipe 不再释放下游仍在使用的 buffer;core 把 channel fd 接收错误改为非致命。

同分支后续版本

1.31.7(2026-10-14):原生 GeoIP 模块支持 MaxMind MMDB,geoip_country/geoip_city/geoip_org 指向 .mmdb;$geoip_country_code3 由内置 ISO 表推导,$geoip_org 依次查 autonomous_system_organization/isp/organization/domain,$geoip_dma_code 可能为空,$geoip_area_code 永不设置。HTTP/2 实现 RFC 9218 优先级(Priority: u=1 / u=5, i),旧的 RFC 7540 依赖树与权重被移除、旧优先级信号被忽略;测试中 50Mbit/s 链路上两个 10MB 下载,后发的 u=1 请求 2.57s 完成,先发的 u=5 请求 3.42s 完成。QUIC 的 BPF reload 与二进制升级也有改进。

1.31.8(2026-10-21):working_directory 路径改为相对 nginx prefix;修复严格对齐平台上的 HTTP/3 SIGBUS;OpenSSL 3.2+ kernel TLS 关闭 SSL 连接时的 recv() EIO 告警。

安全修复(1.30.6 / 1.31.7,2026-10-14):alias+rewrite 缓冲区溢出 CVE-2026-15176、字符串表达式求值缓冲区溢出 CVE-2026-93479、HTTP/3 请求体注入 CVE-2026-95101、TLS session ticket key 轮换导致会话伪造 CVE-2026-97629、ngx_http_ssi_module 与 AIO 的 use-after-free CVE-2026-97205。

复制全文 生成海报 nginx 运维 反向代理

推荐文章

程序员茄子在线接单