這是why技術的第20篇原創文章
本週原本是沒有時間寫技術文章的,爲了周更不斷,想着去把以前發佈在其餘平臺的一篇原創文章搬過來就行。結果發現,當年我寫的那篇文章,離真相還差着十萬八千里。html
而去搜索這個問題時,個人文章是檢索結果的第一個。
java
原文《http請求參數中加號被替換爲空格及請求參數被URLDeCode的記錄》連接以下:
https://www.jianshu.com/p/1a3...程序員
因此爲了不繼續誤導讀者,就算週末"爆肝",也得輸出此文,不得不發。web
這是我做爲程序員的自我修養。面試
以前寫那篇文章的緣由是碰到了兩個有趣的問題,以下:spring
首先,咱們進行場景復現,搭建項目的過程就不說了,用idea+springboot搭建一個簡單的web項目還不是信手拈來的事?apache
正如上面的現象所示:個人入參是jay+love,可是後臺接收到的是jay love,加號變空格了。爲何呢?編程
本文分析的Tomcat源碼版本爲:9.0.29.
瀏覽器
經過Debug能夠找到兩處關鍵的代碼:tomcat
第一處:
org.apache.tomcat.util.http.Parameters#processParameters(byte[], int, int, java.nio.charset.Charset) 下圖中的290行
在這個地方由於有'+',因此把decodeValue參數設置爲true,表示須要對請求中的value進行decode操做。
decode的具體的源碼位置以下,也就是第二處關鍵代碼:
org.apache.tomcat.util.buf.UDecoder#convert(org.apache.tomcat.util.buf.ByteChunk, boolean)
能夠看到,在源碼裏面有一段代碼,是把'+'替換了爲了空格,是特地作了這樣的特殊處理。
整個方法的解讀以下:
因此個人入參是jay+love,可是後臺接收到的是jay love,加號變空格了。爲何呢?
緣由很簡單,在源碼中有一段代碼把'+'替換成了空格,刻意爲之。
以前的文章裏面我寫的是:
因爲歷史緣由,那究竟是什麼歷史緣由呢?
我在網上查了一圈,沒有找到具體的歷史緣由,我看到的全部的關於這個問題的文章,要麼只是給瞭解決方案,要麼就是上面這一句歷史緣由,一帶而過,含糊其辭。
這裏,我就明明白白的告訴你爲啥。
通過我長時間的摸排,我找到了不少蛛絲馬跡,整理以後,我決定從JDK的一個"BUG"講起。
對應連接:http://bugs.sun.com/view_bug....
從提交時間上能夠看出,該問題早在2001年,距今18年前就有人指出來了,並給JDK上報了BUG,他的描述以下:
首先,咱們先把他的測試代碼拿出來跑一下:
他爲何說空格encode以後應該是%20呢?
由於他在BUG裏面提到了RFC2396標準。(RFC就不解釋了,你只要知道是業界認證的權威標準就行):
地址:http://www.ietf.org/rfc/rfc23...
在RFC2396的第2.4.1節,明確的說了:"%20"是US-ASCII空格字符的轉義編碼。
去查詢標準的ASCII碼你也能夠發現確實是這樣的:
用代碼實踐一下,證實以上結論:
看java.net.URLEncoder#encode(java.lang.String, java.lang.String)的源碼也能夠直觀的看到,源碼裏面作了特殊處理:
再看java.net.URLDecoder#decode(java.lang.String, java.lang.String)的源碼:
這裏就和前面的呼應上了,這處理方式,如出一轍呀。因此爲何這樣處理,兩處地方屬於同宗同源啊!
而提BUG的那個哥們爲何以爲這是一個BUG呢?
雖然通過試驗,'+'和'%20'通過decode都能轉化爲空格,可是他認爲,根據RFC2396來說,這裏只能是'%20',怎麼能變成'+'呢?因此他以爲這是一個BUG。
那咱們看看JDK官方是怎麼回覆這個問題的呢?
官方回覆:
*這不是BUG啊,朋友!這個類就是遵循了HTML規範中的規定:如何對 HTML表單中的URLs進行encode。它不打算用於其餘用途。
而這樣作的緣由,是由於包括HTML 4.01第17.13.4節和RFC 1866(已經被W3C HTML推薦標準取代)都是這樣規定的。*
對於第一段話,官方的意思我理解是:這個類就是拿來對url進行encode的,不作其餘用途。由於你調用了encode編碼,那就須要decode解碼,我只要保證你解碼以後的數據和你encode以前的數據是同樣的就好了。你要拿去搞其餘事情,我就管不了了。
而爲何這樣作呢?是由於規定就是這樣的呀,相似於國家標準就是這樣的,相似於產品經理提出的需求就是這樣的呀。這裏官方提出了兩個標準,一個是HTML 4.01,一個是RFC1866(這個已經被其餘的標準取代了,那咱們就只看HTML 4.01)。
*HTML4.01是1999年12月24日發佈的,在HTML4.0基礎上進行微小改進,W3C推薦標準 。
在w3c上找到該標準,地址以下
https://www.w3.org/TR/html401...*
下圖圈起來的地方很關鍵,能夠點開放大查看:
找到HTML 4.01第17.13.4節,其中明確指出:當content-type爲application/x-www-form-urlencoded時,對names和vaules進行轉義,空格用'+'代替。
HTML 4.01第17.13.4節原文以下:
Control names and values are escaped. Space characters are replaced by `+'
官方舉的雖然是HTML 4.01的例子,可是我翻譯了歷史文獻,發現其實在更早的HTML 3.2規範中就規定了,HTML 3.2規範在1996年就成爲了W3C推薦標準,其中相關內容以下:
連接地址:https://www.w3.org/TR/2018/SP...
而application/x-www-form-urlencoded是瀏覽器默認的content-type。
在BUG裏面提到的RFC2396標準是1998年8月提出來的:
而HTML 3.2規範在1996年就成爲了W3C推薦標準。
因此,我以爲這就是歷史緣由!
再說一次,在HTML 4.01規範中就明確規定了:當content-type爲application/x-www-form-urlencoded時,對names和vaules進行轉義,空格用'+'代替。
沒有緣由,就是規定!我在查詢的過程當中發現,其餘的編程語言也有這樣的問題,由於他們都聽從一樣的標準,就有了一樣的"歷史緣由"。
回到前面的這個地方:
這裏解碼的時候爲何把'+'轉化爲空格呢?由於"歷史緣由",若是URLs中出現了空格,須要用'+'替換,因此這裏解碼的時候把'+'轉化回了空格。先有了編碼的操做,因此纔會有解碼的操做。
不少的文章都在說這是'+'的緣由,甚至有的文章說'+'的編碼應該改成%20。可是其實上面分析過了,有問題的是空格,而不是'+'。
那爲何咱們在作表單提交的時候,也常常寫'+'號呀,爲何沒有問題呢?
由於當Html的表單被提交時, 每一個表單域都會被Url編碼以後纔在被髮送,下面的小例子能夠佐證:
解決方案網上一大堆了,我這裏羅列一下吧:
方案一:修改客戶端,將客戶端帶'+'的參數中的'+'所有替換爲'%2B',以下:
方案二:修改服務器端,將空格替換爲'+',這種方式只適用於參數中'+'沒有空格的狀況。以下:
方案三:修改服務器端,將獲取參數的方法由reuqest.getParameter改成request.getQueryString(),而後對獲得的字符串進行解析。
正如我文章最開始說的,就算是熬夜爆肝,我也必須得輸出這篇文章,由於我最開始的文章不只寫的表面,並且還有一些問題,我得對其進行糾正。
讓我忽然想起了以前和朋友的一次對話,他問我說:你做爲程序員,時刻待命,只要系統一出問題你就立馬會響應。你不以爲累嗎?
我回答道:說真的,當系統出問題,須要我排查問題的時候,我不以爲累。由於這個系統是我負責的,代碼是我本身一行行的寫出來的。出現了問題,我得證實個人系統是沒有問題的,是否是別人的打開方式不對。可是若是真的是個人代碼致使的問題,我會心有愧疚,我也得當即響應,對其負責。
這是我做爲一個程序員的自我修養。
這篇文章的風格和《這道面試題我真不知道面試官想要的回答是什麼》有點類似,全文描述的都是很小的知識點,甚至能夠說是冷知識。一句話就能說出表面上的爲何,提煉出一個知識點。
可是我以爲提煉出來的,是一個乾癟癟的知識點,它不夠豐富,沒有探索的過程。
而我所展現的是我去尋找這個問題的答案的過程。經過JDK的"BUG"把幾個協議串聯起來,並且是全世界共同遵循的協議,極具權威性。
才疏學淺,不免會有紕漏,若是你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。
感謝您的閱讀,感謝您的關注。
以上。
歡迎關注公衆號【why技術】。在這裏我會分享一些技術相關的東西,主攻java方向,用匠心敲代碼,對每一行代碼負責。偶爾也會荒腔走板的聊一聊生活,寫一寫書評,影評。願你我共同進步。