關於URL Encoding的那些事

以前遇到一個關於URL encoding的一個問題,很tricky,這裏把這個問題的root cause以及對這個問題的一些思考記錄下來,分享給你們。html

首先,抽象這個問題的原型以下:java

有一個電商平臺,咱們須要調用其暴露的一個API來建立電商信息,API要求咱們把電商名做爲URL參數傳過去,而後建立相應的一條電商記錄。這個API示例以下:node

POST http://localhost:8080/stores/{storeName}

返回結果:算法

{
  "storeName": "xxx",
  "otherInfo": ""
}

在測試這個API的過程當中發現,有一個電商名(abc{d)包含字符「{」,出現了一個問題:用postman發送請求過去可以成功;可是經過java代碼發送請求則報錯,提示說URL syntax出錯,URL不容許包含「{」字符。apache

POST http://localhost:8080/stores/abc{d

後來發現,postman能成功的緣由是因爲postman自動幫忙作了個URL encoding,經過建立出來的那條記錄能夠驗證,建立出來的記錄以下。後端

{
  "storeName": "abc%7Bd",
  "otherInfo": ""
}

而java代碼沒有作encoding,因此就報錯了。由於根據URL的RFC規範,「{」字符確實不容許,必須作encoding。See https://tools.ietf.org/html/rfc3986#section-2服務器

那麼,怎麼解決這個問題呢,如何容許傳入包含「{」字符的電商名呢?當時想到的解決方案是,在代碼中顯式的把電商名都作一個URL encoding,而後再做爲URL參數傳過去。很好,用這個方案把這個問題解決了。請求URL參數電商名是encoded過的(abc%7Bd),建立出來也是encoded過的(abc%7Bd)。mvc

沒過多久,又出現了另一個問題。在測試過程當中,有一個電商名(abc:d)包含字符「:」,按照以前的邏輯,咱們把「:」作一個encoding(abc%3Ad)發送過去,指望建立的記錄應該是encoded的名字,可是結果卻不是,結果是一個decoded的名字(abc:d),即包含「:」自己。ide

結果由於這個,程序出錯了。同時測試還發現,經過postman發送請求過去沒有問題,發送帶字符「:」的名字(abc:d)過去,建立出來的就是帶「:」(abc:d);發送字符「:」的encoded串(abc%3Ad)過去,獲得的就是encoded的串(abc%3Ad)。可是,經過代碼發送字符「:」(abc:d)或者字符「:」(abc%3Ad)的encoded的串,建立出來的都是「:」(abc:d)。到這裏就有點思路了,發現字符「:」好像在哪裏被自動decode了。post

通過調試發現,問題出在一個底層依賴的library的版本上,java發送http請求的方法最終依賴於一個library叫作org.apache.httpcomponents:httpclient,高版本(4.5.8)的library會自動decode一些字符(包括「:」),低版本(4.5.5)的則不會。

那麼到這裏,問題就明白了,問題的root cause便是雖然咱們顯式把字符「:」作了一個encoding,可是在請求被真正發出去以前被Httpclient自動decode了,因此建立出來的記錄都是「:」。

對這個問題的一些思考:

第一,爲啥字符「:」的encoded串會被自動decode,而字符「{」卻沒有?

緣由是,「:」是URL規範容許的字符,儘管其是保留字符。而「{」是不容許字符,必須作URL encoding,中文漢字也是同樣,必須作URL encoding。

第二,若是傳入的是一箇中文字符的encoded串,建立出來的就是encoded的串,很顯然這個不易讀,因此咱們須要把這個encoded的串作一個decode。那麼設想一下,服務端每次都須要顯示作decode嗎?記得以前在Spring mvc項目中沒有顯示decode啊?

緣由是Spring mvc自動把URL參數都作了一個decoding,因此咱們不用顯示decode。而這個問題中的API在實現端沒有利用自動decode功能,即拿的是原生的參數值,因此一些時候會存在不易讀。自動decoding會有什麼問題嗎?在大多數狀況下應該是沒問題,即輸入「{」的encoded串,後端獲得的是就是「{」。那麼若是自己參數值就是「{」的encoded串呢,也沒問題,發請求方本身須要對這個串作一次encoding做爲輸入。

第三,發現自動decoding在不一樣技術棧平臺(Spring boot / mvc, .net core / mvc, .net framework / mvc, Nodejs)實現不同 ,有時候也會出現不一致的狀況。好比說,當請求的URL參數包含%3F(字符「?」的encoded串),在Spring boot和 .net core都可以正常拿到字符「?」;在.net framework裏卻會報錯。而當請求的URL參數包含%2F(字符「/」的encoded串),在Spring boot, .net core和 .net framework裏都不工做;在Nodejs裏,用相對比較原生的方式,就能夠工做而且獲取到這個URL參數。以下:

P1:Spring boot中字符「?」是work的

關於URL Encoding的那些事

P2:Spring boot中字符「/」不work,報404

關於URL Encoding的那些事

P3:Nodejs中能夠拿到包含字符「/」的參數

關於URL Encoding的那些事

因此有時候利用平臺的自動decoding可能會出現一些問題,這時候你可能須要考慮利用平臺相對比較原生的方式操做httprequest對象,好比上面nodejs的方式。

最後,其實關於編碼,以前也寫過一篇關於utf8編碼的文章(關於編碼的那些事),這裏討論的是URL encoding。編碼,我的理解其本質就是把一種表現形式的內容經過某種方式轉換成另一種形式,以達到某些目的,好比URL encoding把字符「{」轉成「%7B」,這樣才符合http規範,URL才能被正確解析及轉發到服務器;好比utf8編碼把字符「漢」轉成「e6b189」,這樣才能被計算機存儲,由於計算機只能識別一個字節一個字節。除了utf8編碼、URL encoding,咱們經常使用到的還有另一種編碼方式:base64編碼,這個編碼主要用於混淆易讀的一些信息,好比jwt token。

注意,咱們在不少地方還用到哈希算法,好比SHA1等,編碼和哈希最大的區別就是:編碼是可逆的,哈希不可逆。

References

相關文章
相關標籤/搜索