用了10多年的 Tomcat 居然有bug,这能忍

作者:Aaron Liu rattan antics

为了解决分布式链路跟踪问题,我们引入了Jaeger来实现OpenTracing。然后我们为SpringBoot框架编写了一个启动器,允许用户实现接近零的转换来访问完整的链接。

由于公司有一个封装SpringBoot的内部框架,因此我们的启动器基于最新框架使用的SpringBoot版本。因此,当服务系统访问时,它需要首先升级框架,然后引入我们的启动器以无缝访问整个链接。

然后有一个遵循这些步骤的业务系统,升级框架,引入启动器并访问全链接系统,并且功能测试压力测试已经过去。结果,我们充满信心地上网了。结果,在线nginx报告了大量的http400错误。

aa2fdd8593e54b1d9f89348b35cf7aee

失败后,业务系统的研发人员检查了所有日志,包括机器上的elk和日志,没有发现明显的错误日志。这是所有的了。

6b5683d9372c40678e22b870575bc188

经过几次挣扎,我仍然没有在日志中找到任何线索。这更加绝望。更奇怪的是,它在测试环境中是正常的,这很奇怪。

然后我们猜测压力测试之前是否做得不够,我们仍然按下压力测试环境,看它是否会重新出现。然后就在此业务系统进行压力测试之前,然后快速找到运行和维护,以建立一个压力测量环境。结果,刚刚施工后面部再现了400误差。

然后将操作和维护学生扔掉,然后在nginx的位置神奇地添加一个线路配置。

Proxy_set_header HOST $ host

93367e0038734d248725d86feb156c8a

然后我开始检查各种配置。

这种配置的主要原因是当nginx转发htp请求时,它会添加实际的Host请求头。如果http请求是,则nginx将主机请求标头(主机: abc.com)转发到后台服务,就像转发http请求时一样。对于nginx,如果未配置proxysetheader HOST $ host,则默认情况下主机将更改为上游的名称。

然后我们在压力测试环境中尝试了修改版本并发现它是正常的。我们的nginx配置大致如下

e41e4b66d77e45998c50913646d9c9fc

然后总结一下当前的现象:

如果未使用proxysetheader HOST $ host配置nginx,则会修改以前的版本。修改后的版本报告400错误。使用proxysetheader HOST $ host配置nginx后,两个版本都正常。

那么我们改变了什么?

升级SpringBoot版本以引入完整链接启动器

然后我们试着查看完整链接启动器的引用,发现它仍然是400错误。然后回滚SpringBoot版本并发现它是正常的

总结一下:这是因为SpringBoot版本的升级导致了这个问题,并且因为它是由http的头部更改引起的,所以可以大胆猜测由于Tomcat版本引起的升级。

Tomcat version upgraded from 8.5.11 to 8.5.3

2631eba8f26d45aeac031d06844d24e4

According to the previous analysis, when nginx is not configured with proxysetheader HOST $host, the name of the upstream will be used as the content of the Host header by default when forwarding the http request.

In other words, the new version of tomcat reported a 400 error in receiving http request for sc_java (underlined)

Let's reproduce this error: as follows, deploy two background services using the new version of tomcat, the ports are 8083 and 8084 respectively

2989f657ab5f4562a4d61f2747e43deb

The nginx configuration is as follows. The point is that the upstream is underlined

808ae012507b4836acea3ef0631e7237

Then use postman to request nginx and reproduce 400 errors

1021286ad1dd4b31aeefa346c7d01150

Adjust the nginx configuration, mainly modifying the upstream to be underlined

024d2039886642949df49e56c07bc404

Then ask again and find that it is normal

40218f7caa2e4b10ae00cd62d46423f6

Roll back the tomcat version. Modify the nginx configuration online: add configuration proxysetheader HOST $host or modify upstream to have no underscore name

Although we know the cause of the failure, we also know how to fix this fault. But I just don't know why the new version of tomcat has this problem. With this question, our group of colleagues searched the next 400 questions in the issue of the SpringBoot project and found that there is a related issue

xx[tomcat] Spring boot web在使用域名时总是返回400

虽然它看起来与我们的相同,但它有400个问题,但具体原因不同。问题是,如果域名.ext包含一个数字,例如'domain.sf1m',则会出现400个问题。此问题也已在新版本的tomcat中得到修复。

但即使我使用最新的8.5.x版本的tomcat,当我使用带下划线的Host的http请求tomcat时,我仍会报告400错误。

换句话说,下划线主机的http请求,tomcat认为这是一个问题

那么为什么以前版本的tomcat正常?有了这个问题,我们将分析tomcat的源代码。

由于我之前没有看过tomcat的源代码,因此很难分析哪行代码存在问题,所以我检查了相关的bugImprove登录AbstractProcessor.parseHost()

这是bug中的错误堆栈

10776d8d01b04727bc5ab6fc9da214b9

找到相应的代码更改如下

d9815268abcb4dd7885d8171ca1a441b

这里我们也知道处理Host头的类是HttpParser类。

然后我这次检查了tomcat8.5.31和8.5.11的代码,并比较了HttpParser和AbstractProcessor类。比较结果如下:

674723d2d1444859bd51ce95173e3e3a

发现8.5.11版本的AbstractProcessor类有一个parseHost方法,然后主解析方法是Host.parse(valueMB);

635fcf3dbd5a476fae9bfca5bec5cf0e

这里我们已经知道为什么8.5.11版本的tomcat是正常的,主要是因为tomcat的8.5.11版本没有验证Host头,但是tomcat的8.5.31版本添加了这个检查。

我们来看看tomcat源代码的提交记录

ae66bab4fc5b4e398ea227b11b3594ae

我们发现主机/端口检查是在2018/4/6上添加的。

那么为什么tomcat会增加对该主机的检查,并且不允许使用下划线主机?实际上,这是一种规范,您可以访问以下地址

好的,这里我们知道,事实上,对于带下划线的Host,tomcat是RFC1-1034的规范,所以tomcat的处理是正确的。但是,tomcat在处理其他一些合法主机的历史中遇到了错误,但是下划线的处理一直是正确的。

因此,在配置上游时nginx无法使用带下划线的名称后,最好将proxysetheader HOST $ host添加到该位置。