拍摄于富平中华郡
背景
今天组内一个小哥找我协助看一个问题,现象是他开放了一个Api给第三方调用,需要在http中传递一个名字为access_token的头,但是发布到测试环境以后却怎么也获取不到这个头,本地调试是没有问题的,希望协助看看。
排查
http传递头还会出问题,这都是很成熟的东西了,大概率是代码写的有问题。我提出看一眼代码,就一行request.getHeader,也不应该出问题,那是哪里出了问题呢?
"弄清楚请求链路,从外到内,从内到外逐个分析"。
冷静下来分析一番以后我觉得可能是请求链路上的其他节点没有正确传递access_token导致,所以我带着小哥大概梳理了一下请求链路,大致是这样(可能和真实的不同,只是按我了解到的):
浏览器->外网nginx->k8s ingress->springboot服务。
接下来就开始从内到外逐个逐个节点测试分析,找出元凶。
1.先进入springboot服务所属的容器内使用curl 127.0.0.1测试,等于自己访问自己,排除其他节点的干扰,结果是可以正常拿到access_token的值,排除代码问题。
curl -H 'access_token: xxxx' http://127.0.0.1:8080/xxx
2.进入ingress的容器内依然是curl 127.0.0.1测试,这时的请求链路相当于curl->k8s ingress->springboot服务,目的是验证k8s ingress是否会影响请求头的传递。
#这里有个细节是多加了Host: 域名,至于为什么这里留个思考题
curl -H 'Host: 域名' -H 'access_token: xxxx' http://127.0.0.1:8080/xxx
3.以此类推测试&分析。
其实在做完第一步的时候我脑袋里突然闪过了答案(很久之前遇到过,一开始没想起来),后面的步骤就没有进行,写在这里只是想把自己排查这类问题的思路和大伙做一个交流,至于答案是什么我先卖个关子,我接下来先普及一个冷知识。
http请求头规范
https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
大概意思是请求头的名称应该是由字母(特指英文字母)、数字、- 组成,最好是以字母开头,还特别说了如果请求头包含_可能带来一些问题,有兴趣的可以去了解,这里就不啰嗦了。
真凶
到这里也许你已经发现了一些端倪,前面提到的access_token不正是包含_吗,从rfc文档来看这个头是不符合规范的,所以就在nginx那里翻了车,一起来看下官网的介绍。
https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
解读下这段文字的意思:nginx会将带有_的请求头丢弃,不会往后传递,如果不希望丢弃可以修改underscores_in_headers的值为on或者将ignore_invalid_headers修改为off。
最终处理
1.梳理链路上的nginx,修改underscores_in_headers或者ignore_invalid_headers的值;
2.将access_token修改为access-token,使其符合规范;
最终采用方案2解决,原因很简单,修改请求链路上的参数影响较大,而且不一定能覆盖全,已知的有nginx,保不齐还有slb、iis、apache等,不可控因素太多。