對於一個編解碼問題的思考

,發表於2016-11-02

做過Java Web開發的人可能都遇到過這樣一個問題,當查詢條件中的引數為中文符號時,傳遞到後臺的引數不能正確解碼。我前一段時間遇到了這個問題,查詢到的解決方案是先在前臺使用JS對引數編碼兩次,然後再在後臺使用Java對其進行一次解碼。這個方案讓我感到很困惑,決定探個究竟。

先說一下開發環境,後臺使用的是JDK 7,應用伺服器使用的是Tomcat 7。

關於編解碼的小常識

在Unicode字元編碼中,每一個漢字字元都有一個唯一編碼,稱為碼點(Unicode code point)。表示一個Unicode值的十六進位制數通常在前面加上“U+”,例如“U+0041”代表字元“A”。

碼點僅僅是為字元賦予了一個唯一的ID值,是一種抽象,並沒有規定字元具體的編碼型別。在實際應用中,在使用字串的時候,需要指明其編碼型別,只談字元不談編碼型別就是耍流氓。(It does not make sense to have a string without knowing what encoding it uses. —— by Joel Spolsky)

最常見的字元編碼型別是UTF-8。網路傳輸中使用的是UTF-8編碼。

言歸正傳,在JS的encodeURIComponent()方法中,使用一到四個轉義序列來表示字串中的每個字元的UTF-8編碼。另外, encodeURIComponent 還會轉義除了字母、數字、(、)、.、!、~、*、'、-和_之外的所有字元。

 > encodeURIComponent("圖靈")
 < "%E5%9B%BE%E7%81%B5"  #UTF-8

當編碼後的字元傳送到後臺,後臺為什麼會出現亂碼呢? 後臺程式碼如下:

 String name = request.getParameter("name"); 
 name = URLDecoder.decode(name, "UTF-8");

原來,當使用request.getParameter()獲得引數值的時候,此方法已經對傳遞過來的引數進行了一次解碼工作,而且很不幸的是,在Tomcat 7及以前的版本中,預設的編碼格式不是UTF-8,而是ISO-8859-1,也就是Latin-1。( 在Tomcat 8中,預設的編碼格式已經改為UTF-8了,參見這裡)。

注意:在使用Tomcat作為容器的時候,方法中出現的HttpServletRequestHttpServletResponse對應的實現類是由Tomcat提供的。

這樣就可以得到答案了。第一次使用encodeURIComponent對字元進行編碼可以得到"%E5%9B%BE%E7%81%B5",第二次會將其中的%進行轉義,轉義為%25,其他字元則保持不變。當此引數值傳遞到後臺的時候,request.getParameter()會首先對其進行一次解碼(使用ISO-8859-1),將其還原為"%E5%9B%BE%E7%81%B5",然後就可以使用URLDecoder.decode(name, "UTF-8")正確解碼了!

經過試驗發現,當將應用伺服器升級為Tomcat 8的時候,僅對其進行一次編碼操作就可以了。還有一個問題,Tomcat 8更改預設編碼格式會不會產生相容性問題?答案是不會,因為UTF-8是相容ISO-8859-1的。

參考資料:

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
  2. http://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2
  3. http://stackoverflow.com/questions/469874/how-do-i-correctly-decode-unicode-parameters-passed-to-a-servlet/470320#470320

後記:

上述方法僅做為分析用途,不推薦在實際專案中使用。 實際上對於GET請求,將server.xml中的URIEncoding設定為UTF-8或者將useBodyEncodingForURI屬性設定為true即可。參見:https://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2

相關文章