'Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?'

Tomcat Java Nginx Java架構師筆記 2019-09-13
"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現對應的代碼改動如下

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現對應的代碼改動如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們也就知道了處理Host頭部的類就是這個HttpParser類。

然後我在本次check了下tomcat8.5.31 和8.5.11的代碼,比對了一下HttpParser以及AbstractProcessor類。對比結果如下:

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現對應的代碼改動如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們也就知道了處理Host頭部的類就是這個HttpParser類。

然後我在本次check了下tomcat8.5.31 和8.5.11的代碼,比對了一下HttpParser以及AbstractProcessor類。對比結果如下:

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現8.5.31版本的AbstractProcessor類中多了一個parseHost的方法,然後主要解析方法是Host.parse(valueMB);

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現對應的代碼改動如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們也就知道了處理Host頭部的類就是這個HttpParser類。

然後我在本次check了下tomcat8.5.31 和8.5.11的代碼,比對了一下HttpParser以及AbstractProcessor類。對比結果如下:

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現8.5.31版本的AbstractProcessor類中多了一個parseHost的方法,然後主要解析方法是Host.parse(valueMB);

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們就已經知道了為什麼8.5.11版本的tomcat是正常的,主要是因為8.5.11版本的tomcat沒有對Host頭部進行校驗,而在8.5.31版本的tomcat增加了該校驗。

我們來看一下tomcat源代碼的提交記錄

"

為了解決分佈式鏈路追蹤的問題,我們引入了實現OpenTracing的Jaeger來實現。然後我們為SpringBoot框架寫了一個starter以讓用戶實現近零改造接入全鏈路。

由於公司有一個封裝了SpringBoot的內部框架,然後我們的starter就以最新框架所使用的SpringBoot版本為基礎進行開發。所以業務系統在接入的時候需要先升級框架,然後再引入我們的starter才行無縫接入全鏈路。

故障描述

然後有一個業務系統就按照步驟,升級框架,引入starter就接入了全鏈路系統,並且功能測試壓力測試都已經通過了。結果我們滿懷信心地就上線了。結果,線上nginx報大量http 400錯誤。

故障排查

出現故障後,業務系統的研發人員查了所有的日誌,包括elk以及機器上的日誌,都沒有發現明顯的錯誤日誌。這個就。。。

幾番掙扎後還是沒有在線上的日誌中找到任何蛛絲馬跡。這個就比較絕望了。更奇怪的是在測試環境中是正常的,這個就比較詭異了。

然後我們猜想是不是之前壓力測試做得不夠啊,我們還是在壓測環境中再壓測一下看看會不會復現。然後正好之前這個業務系統做過壓測,那就趕緊找運維搭建一個壓測環境。結果剛搭建完就非常給面子地復現了400錯誤。

然後運維同學就各種折騰,然後神奇般地在nginx中的location下加了一行配置後就好了.

proxy_set_header HOST $host

然後就開始各種查這個配置是啥意思。

這個配置的主要是在nginx在轉發htp請求的時候會加上實際的Host請求頭。如http請求是 http://abc.com/hello,那麼nginx在轉發http請求的時候會原封不動的把host請求頭(Host:abc.com)轉發給後臺服務。對於nginx而言,如果沒有配置proxysetheader HOST $host的時候會默認修改Host為upstream的名稱。

然後我們又在壓測環境中試了一下修改之前的版本,發現是正常的。我們nginx的配置大體如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

那總結一下現在的現象:

  • 在nginx沒有配置proxysetheader HOST $host的時候,修改之前的版本是正常的,修改之後的版本報400錯誤
  • 在nginx配置了proxysetheader HOST $host之後,兩個版本都是正常的

那我們到底修改了什麼呢?

  • 升級SpringBoot的版本
  • 引入全鏈路starter

然後我們試了下去掉全鏈路starter的引用,發現還是400錯誤。然後再回退SpringBoot版本,發現是正常的

綜上:是因為升級了SpringBoot版本導致了該問題,又因為是http的頭部變化導致的問題,故可以大膽猜測是因為升級了Tomcat版本導致的該問題

tomcat版本從8.5.11升級到8.5.31

故障本地復現

由前面的分析可知,nginx在沒有配置proxysetheader HOST $host 的時候,在轉發http請求的時候會默認把upstream的名稱作為Host頭部的內容。

也就是說新版的tomcat在接收Host為sc_java(帶有下劃線)的http請求報了400錯誤

下面我們來複現一下這個錯誤:如下,本地部署兩個使用新版本tomcat的後臺服務,端口分別為8083和8084

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

nginx配置如下。重點是upstream是帶下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後使用postman請求nginx,復現400錯誤

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

調整nginx配置,主要修改upstream為沒有下劃線的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

然後再請求,發現是正常的

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

故障修復方案

  • 回退tomcat版本。代價較大
  • 線上修改nginx配置:加上配置proxysetheader HOST $host 或者修改upstream為沒有下劃線的名稱

根因分析

我們雖然知道了故障的原因,也知道了怎麼修復這個故障。但是就是不知道新版的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的相關的bugImprove logging in AbstractProcessor.parseHost()

下面是bug中的錯誤stack

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現對應的代碼改動如下

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們也就知道了處理Host頭部的類就是這個HttpParser類。

然後我在本次check了下tomcat8.5.31 和8.5.11的代碼,比對了一下HttpParser以及AbstractProcessor類。對比結果如下:

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

發現8.5.31版本的AbstractProcessor類中多了一個parseHost的方法,然後主要解析方法是Host.parse(valueMB);

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

到這裡我們就已經知道了為什麼8.5.11版本的tomcat是正常的,主要是因為8.5.11版本的tomcat沒有對Host頭部進行校驗,而在8.5.31版本的tomcat增加了該校驗。

我們來看一下tomcat源代碼的提交記錄

Java架構師筆記丨用了10多年的 Tomcat 居然有bug,這能忍?

我們發現在 2018/4/6增加了對host/port的校驗。

跟因之跟因

那為什麼tomcat增加了這個Host的校驗呢,而且不允許使用帶有下劃線的Host呢?實際上這個是有規範的

經驗教訓

好了,到這裡我們就知道了,其實對於帶有下劃線的Host,tomcat是遵循的RFC1-1034的規範的,所以tomcat的處理是正確的。但是tomcat在處理某些其他合法的Host的時候歷史上出現過bug,但是對於下劃線的處理一直是正確的。

所以,以後nginx在配置upstream的時候不能使用帶有下劃線的名稱,還有最好在location位置上加上proxysetheader HOST $host。

附:學習資料「鏈接」

"

相關推薦

推薦中...