Nginx的sub_filter指令可在代理响应返回前对文本响应体进行字符串替换,适用于修复第三方API中的绝对URL;需关闭压缩、显式设置sub_filter_types、启用sub_filter_once off,并注意其不处理响应头、二进制内容及非代理资源。

Nginx 的 sub_filter 指令可用于在代理响应返回给客户端前,对响应体(body)进行字符串替换,特别适合修复第三方 API 响应中硬编码的绝对路径(如 https://api.example.com/v1/),将其重写为当前域名下的相对或代理路径(如 /api/v1/)。但需注意:它仅作用于文本响应(Content-Type: text/*, application/json, application/javascript 等),且默认不处理压缩内容、不支持正则、不递归匹配。
确保 sub_filter 生效的基本前提
必须启用 sub_filter 并显式开启响应体处理:
- 使用
sub_filter指令定义原始字符串和替换值(支持变量,如$scheme,$host,$request_uri) - 添加
sub_filter_once off允许多次替换(默认只替换第一个匹配项) - 添加
sub_filter_last_modified off(可选)避免因替换导致Last-Modified头与响应体不一致而被缓存拒绝 - 关键:必须关闭响应压缩(gzip / brotli),否则
sub_filter无法处理压缩后的内容 —— 在location块中加gzip off;或gzip_disable "msie6";(更稳妥是全局禁用或按 location 关闭)
修复 JSON 响应中的绝对 URL 字段
假设第三方 API 返回 JSON 中包含 "avatar": "https://up.example.com/u/123.jpg",你想改为 "avatar": "/uploads/u/123.jpg":
- 在 proxy location 中配置:
location /api/ {
proxy_pass https://api.upstream.com/;
proxy_set_header Host api.upstream.com;
<pre class='brush:php;toolbar:false;'># 关键:关闭压缩
gzip off;
# 替换所有匹配的绝对 URL 前缀
sub_filter 'https://up.example.com/' '/uploads/';
sub_filter 'https://api.example.com/' '/api/';
sub_filter_once off;
sub_filter_last_modified off;
# 可选:显式声明响应类型,确保 Nginx 正确识别可过滤的 MIME 类型
sub_filter_types application/json text/plain application/javascript;}
注意:sub_filter_types 必须显式包含 application/json,否则默认只处理 text/html。
动态构造替换值(利用变量)
若需将第三方域名统一映射到当前请求的协议+主机,可用 Nginx 变量提升灵活性:
- 例如把
https://cdn.other.com/img/→https://yourdomain.com/cdn/img/ - 写法:
sub_filter 'https://cdn.other.com/' '$scheme://$host/cdn/'; - 变量在运行时求值,但注意:
sub_filter不支持嵌套变量或复杂表达式,仅支持基础变量拼接 - 若需更灵活的重写(如路径截断、正则提取),
sub_filter不适用,应考虑用 OpenResty + Lua 或后端中间层处理
常见陷阱与绕过方案
-
响应头未更新:如果第三方返回了
Location、Content-Location等含绝对路径的响应头,sub_filter无法修改它们 —— 需配合proxy_redirect(针对Location)或add_header+ Lua(通用头) -
HTML 中的 JS/CSS 资源路径失效:若 HTML 里内联了
fetch("https://..."),sub_filter可替换;但若 JS 文件本身由第三方提供且未被代理(即直接<script src="https://cdn.com/app.js">),Nginx 不会处理该 JS 内容 —— 此时需反向代理 JS 资源并同样配置sub_filter -
二进制或非文本响应被跳过:图片、PDF、Protobuf 等不会被处理,
sub_filter_types也无法覆盖 —— 这是设计限制,勿强行尝试


















