問題描述
優付OMS站點,本地在IDEA里用jetty9.4.*啟動程序時,瀏覽器訪問效果如下,頁面錯亂。
而把程序部署到測試環境是可以正常顯示的。測試環境的web服務器是tomcat。
然后,本地用tomcat來啟動程序,發現在瀏覽器里也是可以正常訪問的。
經排查,點擊左側導航菜單打開功能頁是如下這樣實現的。可以看到,路徑上存在雙斜杠//,即比正常路徑多了一個斜杠/。
同樣的代碼,為什么tomcat正常,而jetty卻不正常呢?
問題定位
我們接下來直接基於這個功能頁的url來排查。首先,不帶雙斜杠的url(http://localhost:8080/PCBossMgr/soho/showSaleInfoPage.html)在jetty或tomcat下都是ok的。所以,我們的關注點聚焦在帶雙斜杠的url(http://localhost:8080/PCBossMgr//soho/showSaleInfoPage.html)上。
本地服務器為jetty9.4.*時,瀏覽器網絡請求如下。其中,Network里第一個請求http://localhost:8080/PCBossMgr//soho/showSaleInfoPage.html請求的是spring web controller的action方法,正常返回200;而之后的靜態文件,如css、js,則返回了404。從請求地址 http://localhost:8080/PCBossMgr//layui/css/index.css、http://localhost:8080/PCBossMgr//layui/layui.js里可以看到,這些url也是多了一個/,卻無法正常訪問。
再看tomcat的網絡請求,無論是請求后台的action,還是靜態的css、js資源文件,都是200。
靜態資源url里多了斜杠"/",為什么用tomcat可以正常訪問,而用jetty卻404呢?
看來,tomcat與jetty兩個web容器對url的處理方式不同。
排查結果
我找來最有技術靈性的小王傑同學一起排查原因。如下是最終的分析結果。
URI的RFC3986規范里,對請求路徑(https://datatracker.ietf.org/doc/html/rfc3986#section-3.3,章節3.3 Path)有如下說明:
If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character. If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//"). In addition, a URI reference (Section 4.1) may be a relative-path reference, in which case the first path segment cannot contain a colon (":") character. The ABNF requires five separate rules to disambiguate these cases, only one of which will match the path substring within a given URI reference. We use the generic term "path component" to describe the URI substring matched by the parser to one of these rules.
翻譯為:
如果URI包含權限組件,則路徑組件必須為空或以斜杠(“/”)字符開頭。如果URI不包含授權組件,則路徑不能以兩個斜杠字符(“/”)開頭。此外,URI引用(第4.1節)可以是相對路徑引用,在這種情況下,第一個路徑段不能包含冒號(“:”)字符。ABNF需要五個獨立的規則來消除這些情況的歧義,其中只有一個規則將匹配給定URI引用中的路徑子字符串。我們使用通用術語“路徑組件”來描述由解析器匹配到這些規則之一的URI子字符串。
就是說,
通常情況下,RFC3986規范不允許url路徑里包含兩個斜杠“//”。
在stackoverflow社區發現這么一篇帖子,Is a URL with // in the path-section valid? 文末回帖堪稱精辟:yes, it is valid, no, don't use it.
再來說jetty,jetty嚴格遵守了RFC3986規范。也就是說,jetty不允許url里帶兩個斜杠,它會認為帶有//的url是模棱兩可的路徑(ambiguous empty segment)。怎么講?假如controller的action方法映射的路徑有xxx/{var}/someurl,web靜態目錄里也有xxx/somedir/somefile,那么,當你訪問url包含xxx//x的時候,jetty無法做出選擇。所以,jetty直接來了個痛快的,不支持這種形式的url,以免造成歧義。
tomcat呢,tomcat “違背規范” ,tomcat直接把請求路徑里包含的多個連續的斜杠替換成單個的斜杠,比如 xxx//someurl 會被替換為 xxx/someurl。況且實際在我們的web系統中,也難免會出現一些帶有雙斜杠的url。tomcat兼容了這種情況還是比較得民心的。
附圖-tomcat替換多個斜杠為一個斜杠
附圖-jetty中Violation枚舉,羅列了URI路徑中存在的各種“禁忌”,其中就有本文遇到的雙斜杠問題。
violation 英 [ˌvaɪə'leɪʃ(ə)n] 美 [ˌvaɪə'leɪʃ(ə)n] n. 違反;違法;違章;越軌;侵犯;破壞;違例;犯規 ambiguous 英 [æmˈbɪɡjuəs] 美 [æmˈbɪɡjuəs] adj. 模棱兩可的;含混不清的;不明確的
附圖-文件名/文件目錄名所不允許出現的字符,其中包括斜杠字符
看一篇文章可能只需要十幾分鍾,但寫一篇文章少則需要兩個小時(內容編排,尋找理論支撐等等),感謝各位訂閱與閱讀!