一、问题现象
项目使用 Nginx + SpringCloud Gateway 架构,自定义请求头如下:
app_name: EPOSPC
app_version: 616
出现非常经典的环境差异化BUG:
-
本地开发环境 :直接请求SpringCloud Gateway,可正常获取到
app_name、app_version两个请求头,业务正常运行。 -
线上生产环境:经过Nginx反向代理转发后,Gateway无法获取到这两个下划线格式的自定义请求头,导致业务逻辑异常。
核心特征:无代码变更、仅代理层级不同、带下划线Header线上丢失。
二、根本原因(核心知识点)
问题并非SpringCloud Gateway导致,而是Nginx的默认机制引发的经典坑点。
Nginx 默认参数 underscores_in_headers 默认为 off ,会**_** 静默丢弃所有名称包含下划线 的HTTP请求头,且不会打印任何错误日志,排查难度极高。
补充原理 :HTTP标准本身允许请求头使用下划线,Nginx该设计是源于CGI历史遗留问题。HTTP请求头中的横杠
-和下划线_映射到CGI变量时都会转为下划线,为避免变量命名歧义、参数冲突,Nginx默认禁用了下划线请求头。
简单总结:
-
本地无Nginx代理 → 请求头完整透传 → Gateway正常获取
-
线上有Nginx代理 → 下划线Header被过滤 → Gateway接收不到参数
三、三种解决方案(按需选择,推荐方案一)
方案一:修改Nginx配置(推荐,零业务代码改动)
开启Nginx下划线请求头支持,全局或站点单独配置,无需修改任何Java业务代码,稳定性最高。
配置规则 :该指令不支持写在location块中 ,仅可配置在 http{} 全局块或 server{} 站点块。
完整可用Nginx配置示例:
http {
# 全局开启:允许请求头包含下划线,默认off
underscores_in_headers on;
server {
listen 80;
server_name 你的域名;
location / {
# 常规代理透传配置
proxy_pass http://你的gateway服务地址;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
生效步骤(必须执行,避免配置错误):
# 校验Nginx配置合法性
nginx -t
# 平滑重载配置,不中断业务
nginx -s reload
方案二:规范请求头命名(符合HTTP标准,无需改Nginx)
HTTP官方规范**-_** 推荐请求头使用横杠 而非下划线,从根源规避Nginx限制,适合可以调整客户端的场景。
修改客户端请求头:
-
原:
app_name: EPOSPC→ 新:app-name: EPOSPC -
原:
app_version: 616→ 新:app-version: 616
适配说明:SpringCloud Gateway会自动将 app-name 映射为 appName,后端Java代码无需修改取值逻辑,适配成本极低。
方案三:Nginx变量中转(无法开启全局下划线配置时使用)
若服务器不允许开启 underscores_in_headers on(全局约束),可通过Nginx变量捕获下划线请求头,重命名后转发给Gateway。
location / {
# 捕获原生下划线请求头,重新定义为横杠格式透传
proxy_set_header X-App-Name $http_app_name;
proxy_set_header X-App-Version $http_app_version;
proxy_pass http://你的gateway服务地址;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
后端Gateway直接读取 X-App-Name、X-App-Version 即可获取参数。
四、Gateway端排查验证代码(精准定位问题)
在Gateway全局过滤器中添加请求头日志打印,可快速验证上游透传的请求头,区分是Nginx过滤问题还是后端取值问题。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
// 打印所有请求头,完整查看上游入参
log.info("【Gateway全局请求头】所有header:{}", headers);
// 精准打印目标参数
log.info("【客户端版本信息】app_name={}, app_version={}",
headers.getFirst("app_name"),
headers.getFirst("app_version"));
return chain.filter(exchange);
}
日志对照结论:
-
本地日志:正常打印
app_name、app_version参数值 -
线上日志:无该两个请求头字段,100%确定为Nginx过滤导致丢失
五、高频踩坑总结(避坑必看)
-
配置位置错误 :
underscores_in_headers on写在location块中完全无效,必须配置在http或server块。 -
无日志提示:Nginx丢弃下划线请求头属于静默操作,不会输出任何