proxy_set_header HOST $host
然后就开始各种查这个配置是啥意思。
这个配置的主要是在nginx在转发htp请求的时候会加上实际的Host请求头。如http请求是 http://abc.com/hello,那么nginx在转发http请求的时候会原封不动的把host请求头(Host:abc.com)转发给后台服务。对于nginx而言,如果没有配置proxy_set_header HOST $host的时候会默认修改Host为upstream的名称。
然后我们又在压测环境中试了一下修改之前的版本,发现是正常的。
我们nginx的配置大体如下
在nginx没有配置proxy_set_header HOST $host的时候,修改之前的版本是正常的,修改之后的版本报400错误
在nginx配置了proxy_set_header HOST $host之后,两个版本都是正常的
那我们到底修改了什么呢?
升级SpringBoot的版本
引入全链路starter
然后我们试了下去掉全链路starter的引用,发现还是400错误。然后再回退SpringBoot版本,发现是正常的
综上:是因为升级了SpringBoot版本导致了该问题,又因为是http的头部变化导致的问题,故可以大胆猜测是因为升级了Tomcat版本导致的该问题。
tomcat版本从8.5.11升级到8.5.31
故障本地复现
由前面的分析可知,nginx在没有配置proxy_set_header HOST $host 的时候,在转发http请求的时候会默认把upstream的名称作为Host头部的内容。
也就是说新版的tomcat在接收Host为sc_java(带有下划线)的http请求报了400错误
下面我们来复现一下这个错误:
如下,本地部署两个使用新版本tomcat的后台服务,端口分别为8083和8084
我们虽然知道了故障的原因,也知道了怎么修复这个故障。但是就是不知道新版的tomcat为什么出现这个问题。带着这个疑问,我们组的同事在SpringBoot项目的issue中搜索了下400问题,发现确实有相关的issue
[tomcat] Spring boot web always return 400 when use a domain name
虽然看上去跟我们的问题是一样的,都是400问题,但是具体发生的原因是不一样的。这个issue是说,如果domain name .ext 包含数字,比如 "domain.sf1m",会出现400问题。这个问题也已经在tomcat的新版本中修复了。
但是即使我使用最新的8.5.x版本的tomcat,用带有下划线的Host的http去请求tomcat的时候依然会报400错误。
也就是说,带有下划线的Host的http请求,tomcat认为是有问题的
那为什么之前版本的tomcat是正常的呢?带着这个疑问我们来分析一下tomcat的源代码。
由于之前没有看过tomcat的源代码,所以要分析出到底是哪一行代码有问题是很困难的,所以我查看了下tomcat的相关的bug
Improve logging in AbstractProcessor.parseHost()
下面是bug中的错误stack
到这里我们也就知道了处理Host头部的类就是这个HttpParser类。
然后我在本次check了下tomcat8.5.31 和8.5.11的代码,比对了一下HttpParser以及AbstractProcessor类。
对比结果如下:
到这里我们就已经知道了为什么8.5.11版本的tomcat是正常的,主要是因为8.5.11版本的tomcat没有对Host头部进行校验,而在8.5.31版本的tomcat增加了该校验。
我们来看一下tomcat源代码的提交记录
我们发现在 2018/4/6增加了对host/port的校验。
跟因之跟因
那为什么tomcat增加了这个Host的校验呢,而且不允许使用带有下划线的Host呢?实际上这个是有规范的。具体点击这个链接
rfc1034。
如果你你觉得这个太长了,简单地可以看一下这篇文章
域名中不应出现下划线