升级tomcat遇到的坑

注: 该篇是由于阅读一篇公众号文章看到升级tomcat会发生这个坑,这里记录以下,以便在之后的工作中遇到这个坑时,很方便的解决这个问题。
参考地址:https://mp.weixin.qq.com/s/_orImnCJqQlOTvATPf0o-A

故障描述

tomcat版本从8.5.11升级到8.5.31后,线上nginx 报400错误,ng配置如下:
升级tomcat遇到的坑

解决方法

  • 在nginx中的location下加了一行配置后就好了,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配置如下。重点是upstream是带下划线的升级tomcat遇到的坑
    然后使用postman请求nginx,复现400错误
  • 调整nginx配置,主要修改upstream为没有下划线的
    升级tomcat遇到的坑
    然后再请求,发现是正常的
  • 也就是说新版的tomcat在接收Host为fav_tomcat(带有下划线)的http请求报了400错误

根因分析

我们虽然知道了故障的原因,也知道了怎么修复这个故障。但是就是不知道新版的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
升级tomcat遇到的坑
发现对应的代码改动如下
升级tomcat遇到的坑
到这里我们也就知道了处理Host头部的类就是这个HttpParser类。
然后我在本次check了下tomcat8.5.31 和8.5.11的代码,比对了一下HttpParser以及AbstractProcessor类。
对比结果如下:
升级tomcat遇到的坑
发现8.5.31版本的AbstractProcessor类中多了一个parseHost的方法,然后主要解析方法是Host.parse(valueMB);
升级tomcat遇到的坑
到这里我们就已经知道了为什么8.5.11版本的tomcat是正常的,主要是因为8.5.11版本的tomcat没有对Host头部进行校验,而在8.5.31版本的tomcat增加了该校验。

经验教训

好了,到这里我们就知道了,其实对于带有下划线的Host,tomcat是遵循的RFC1-1034的规范的,所以tomcat的处理是正确的。但是tomcat在处理某些其他合法的Host的时候历史上出现过bug,但是对于下划线的处理一直是正确的。所以,以后nginx在配置upstream的时候不能使用带有下划线的名称,还有最好在location位置上加上proxy_set_header HOST $host