中文化和國際化問題權威解析之五:URL編碼/Misc

通過前面中文化、國際化問題解決的系列1-4,相信大家對字符集、字元編碼、字元解碼、字元亂碼、Java中文問題解決等都有了一個比較清晰的認識;但文中的信息並非包羅萬象,結合到自己平時對於字符集、編碼相關的一些疑惑,本篇對一些前文中並未提及的一些問題進行補充,以便讓該系列更加完善和全面;本文主要解決以下兩個問題:其一,解決UltraEdit菜單中的 文件 -> 轉換 子菜單中涉及的一些名詞疑惑,主要涉及EBCDIC 、OEM字符集、ANSI字符集、HZ編碼等;其二,補充關於URL編碼相關的一些知識點,涉及瀏覽器、Web伺服器設置、Servlet規範等;

更多的與字符集相關的專業辭彙簡介

相信很多同學都有在用UltraEdit這個文本編輯器軟體,從菜單 文件--> 轉換 進入即可看見很多的字符集間的轉換子菜單,不同版本間可能會有些差異,可以參考下圖:

從上圖中我們可以看到很多前面已經提及的一些辭彙,比如ASCII、Unicode [Big Endian/Little Endian]、UTF-8等;但還有一些未提及的,下面就逐個介紹下:

  • l DOS/UNIX/MAC間的轉換:這幾項和字符集無關,主要是由於各個系統對行結束字元的要求不一樣,體現在:Mac格式只要求回車、Unix只要求換行、DOS則要求回車、換行 二者組合;這幾個命令要做的就是對這幾個特殊字元進行相應替換;
  • l EBCDIC (Extended Binary Coded Decimal Interchange Code):他是與ASCII同時期的一種字元編碼,主要用於IBM計算機中;它採用8位碼, 有256個編碼狀態, 但只選用其中一部分。0-9十個數字元的高4位編碼為1111, 低4位仍為0000-1001。大、小寫英文字母的編碼同樣滿足正常的排序要求, 而且有簡單的對應關係, 即同一個字母的大小寫的編碼值僅最高的第二位的值不同, 易於識別與變換。
  • l OEM字符集:由於ASCII碼只利用了低7位,第8位沒有被使用,而隨著計算機的發展,很多原始設備製造商(當時最有名的應該就是IBM了)覺得ASCII碼已經無法滿足需求了,所以他們各自對第8位進行了自定義擴展,從而形成了五花八門的OEM字符集;由於這些擴展都是不通用的,只有各自能夠解釋,這些不兼容的擴展又叫做代碼頁(Code Page),在同一台機器中,通過不同的代碼頁就可以實現系統間信息的轉換了;
  • l ANSI字符集:正如前面提到的,眾多的OEM廠商對第8位進行了自定義擴展;但更加誇張和恐怖的事情是,隨著計算機進入亞洲,此時卻發現整整用完8位也無法將所有的他們需要的語言字元;於是在ASCII的基礎上他們再次進行了擴展,從而產生了雙位元組字符集DBCS(double byte character set),我們常見的國標系列GB2312/GBK/BIG5......都屬於此類;這些字符集有一個共同的特點:完全兼容ASCII!這是ANSI系列和Unicode系列最大的不同點;這類型基於ASCII碼進行擴展的多位元組字符集MBCS統稱為ANSI字符集;注意:ANSI字符集之間也是無法直接識別的,需要進行自定義轉換;
  • l 提到國標系列,在此我還想提一個編碼:HZ碼,HZ是漢字的簡稱?他是基於GB字符集的一種簡體中文編碼,在新加坡被廣泛使用;編碼規則為:他將每個位元組中的第8位(在GB字符集中該位為1)屏蔽,只保留低7位,並將經過變換後的部分用~{、~}括起來;解碼的時候只需要將括弧對裡面的部分高位還原成1即可;這樣就可以在互聯網上廣泛的傳輸了,而不必受制於某些系統只能接收基於ASCII碼的字元;
  • URL編碼

    基於Java開發的Web應用URL組成如下:http://domain:port/contextPath/servletPath/pathInfo?queryString其中各個部分含義如下:Domain、Port:分別是域名和埠;contextPath:應用上下文路徑,默認為應用名稱,比如我們的apps;但可以通過應用伺服器的相關配置進行修改,一般線上環境會修改成/,此時相當於contextPath為空;servletPath:Servlet路徑,一般在應用的web.xml文件中配置servlet-mapping;但由於現在的web應用一般都會用一些框架,比如Struts、Webwork等,此時各框架都會對此進行封裝,會在另外的配置文件中進行設置;但原理都是一樣的;pathInfo:可以理解為最終接收用戶請求的具體執行類,比如我們常說的Action;queryString:get方式傳入的請求參數;以上各個部分中可能存在中文問題的是pathInfo、queryString兩個部分;

    首先,我們來看下Servlet中和URL相關的一些api及其注意事項:HttpServletRequest.setCharacterEncoding(); //僅僅只適用於設置post提交的request body的編碼而不是設置get方法提交的queryString的編碼。該方法還告訴應用伺服器應該採用什麼編碼解析post傳過來的內容;注意:若沒有設定characterEncoding,則使用ISO-8859-1來解碼用戶輸入的表單,而不是使用系統默認的編碼。HttpServletResponse.setContentType(); //告訴瀏覽器網頁中數據是什麼編碼;表單提交時,根據ContentType指定的charset對錶單中的數據編碼,然後發送給伺服器。HttpServletRequest.getParameter("name"); //返回的字元串為:queryString(包括get和post),其值經過Servlet伺服器URL Decode過的,默認編碼來源於應用伺服器中的配置,比如tomcat中server.xml的URIEncoding。HttpServletRequest.getPathInfo(); //返回的字元串為:pathinfo;由Servlet伺服器解碼(decode)過的。默認編碼同上,tomcat中可設置useBodyEncodingForURI。HttpServletRequest.getRequestURI(); //返回的字元串為:contextPath/servletPath/pathinfo;注意是瀏覽器提交過來的原始數據,未被Servlet伺服器URL Decode過。

    對URL編碼【URL Encoding/Percent Encoding】時,使用以下規則:字母數字字元 "a" 到 "z"、"A" 到 "Z" 和 "0" 到 "9" 保持不變。特殊字元 "."、"-"、"*" 和 "_" 保持不變。空格字元 " " 轉換為一個加號 "+"。所有其他字元都是不安全的,因此首先使用一些編碼機制將它們轉換為一個或多個位元組。然後每個位元組用一個包含 3 個字元的字元串 "%xy" 表示,其中 xy 為該位元組的兩位十六進位表示形式。推薦的編碼機制是 UTF-8。但是,出於兼容性考慮,如果未指定一種編碼,則使用相應平台的默認編碼。

    假定我們待請求URL為:http://localhost:8080/example/中國?name=中國;Html內content-type或meta中的charset=GBK;文件格式為ANSI/ASCII;URL中的兩個漢字"中國"的各字符集下的編碼為:

    漢字 編碼 二進位表示
    中國 UTF-8 0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]
    中國 GBK 0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]
    中國 ISO8859-1 0x3f 0x3f[63, 63][??]

    對於Get方式的URL請求有兩種情況,其一:用戶直接在瀏覽器地址欄中輸入URL,此時瀏覽器沒有編碼可參考,直接用瀏覽器的默認編碼進行解析並提交到服務端;其二:在form表單內提交,只是form屬性method為GET,此時瀏覽器會參考目前html中對編碼的相關設置進行解析,比如content-type或meta中的charset。

    以下就重點講講第二種方式的提交:GET方式form submit:瀏覽器會對URL進行URL encoding,然後發送給伺服器。

  • 對於中文IE,如果在高級選項中選中總以UTF-8發送(默認方式),則PathInfo在URL Encoding時按照UTF-8編碼;QueryString按照GBK編碼。此時提交是:GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
  • 對於中文IE,如果在高級選項中取消總以UTF-8發送,則PathInfo和QueryString在URL encoding時按照GBK編碼。此時提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
  • 對於中文firefox、Chrome,因為沒有類似IE中的這種設置選項,所以這兩種瀏覽器中對pathInfo的編碼規則沒有做特殊處理,MS是和queryString採取同樣的編碼規則;在本例中的假設情況下:pathInfo和queryString在URL encoding時都按照GBK編碼。此時提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
  • 很顯然,不同的瀏覽器以及同一瀏覽器的不同設置,會影響最終URL中PathInfo的編碼,該編碼可能不會由我們應用來控制;對於queryString,則是可以由我們的應用來完全控制的,對於上面的事例:中文的IE和FIREFOX都是採用GBK編碼queryString。

    若調整下上例中的假設條件,設置Html內content-type或meta中的charset=UTF-8;此時在IE中queryString會按照UTF-8進行編碼,即name=%E4%B8%AD%E5%9B%BD;但是在非IE(Firefox、Chrome)中,此時提交時URL中會以中文直接提交,即name=中文;此時服務端的web伺服器上肯定要進行相應的編碼配置,否則肯定會出現亂碼;

    若設置Html內content-type或meta中的charset=ISO-5899-1;此時在IE、Firefox、Chrome中queryString都被用ISO-5899-1編碼了,即name= %26%2320013%3B%26%2322269%3B;對於編碼串中的%26、%3B應該是百分號編碼【Percent Encoding】中的保留字元,分別對應&、;,兩者之間是經過編碼的十進位碼;對於這點偶也不是十分肯定?要是有同學比較清楚,請告訴偶下,thx。

    POST方式提交:表單中的參數值對是通過request body發送給伺服器,此時瀏覽器會根據網頁的ContentType("text/html; charset=GBK")中指定的編碼進行對錶單中的數據進行編碼,然後發給伺服器。在伺服器端的程序中我們可以通過Request.setCharacterEncoding() 設置編碼,然後通過request.getParameter獲得正確的數據。

    小結:

  • 1. URL中的PathInfo字元串的編碼和解碼是由瀏覽器和應用伺服器的配置決定的,我們的程序不能設置,不要期望用request.setCharacterEncoding()方法能設置URL中pathInfo解碼時的字符集,這在servlet規範裡面有說明。
  • 2. URL中如果含有中文等非ASCII字元,則瀏覽器會對它們進行URLEncode。為了避免瀏覽器採用了我們不希望的編碼,所以最好不要在URL中直接使用非ASCII字元,而採用URL Encode編碼過的字元串%xy;對此建議URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
  • 3. URL中PathInfo和QueryString應該採用相同的編碼,pathInfo最好不要用中文;因為不同瀏覽器對URL中PathInfo和QueryString編碼時採用的字符集不同,但應用伺服器對URL通常會採用相同的字符集來解碼。
  • 4. URL中的queryString的編碼方式是依賴於網頁的contentType、meta中的charset字符集設置的,記住這點對於form get方式提交亂碼問題排查是非常有好處的。
  • 5. 頁面文件的編碼最好和頁面內設置的charset一致,因為我們可能在頁面內部有很多中文,若編碼不一致,則可能出現在編輯器內看著是正常的,但到了瀏覽器上則變成了亂碼(雖然頁面顯示為亂碼,但這並不意味著在form submit時數據會有問題,很可能出現負負為正的情況);這點在前面寶寶的文檔中也有說明;另外,文件的編碼很可能會影響問題的排查;比如上例中我們就已說明:文件格式為ANSI/ASCII,若文件格式為Unicode/UTF-*系列,那測試結果會不會不一樣呢?大家可以自行測試下;
  • 6. 服務端URL Encode時最好指定字符集,否則encoding的字符集依賴於本地系統的默認編碼。可以參考javadoc:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
  • 相關文檔參考:字元,位元組和編碼:http://www.regexlab.com/zh/encoding.htm各種字符集和編碼詳解:http://blog.csdn.net/ancky/archive/2008/01/11/2034809.aspx深入淺出URL編碼:http://blog.csdn.net/yzhz/archive/2007/07/03/1676796.aspxjavascript html 相關編碼問題研究:http://stauren.net/log/fpev3c89q.htmlJ2EE Web組件中中文及相關的問題(系列):http://blog.csdn.net/whodsow/archive/2003/10/27/19465.aspx

    推薦閱讀:

    修表是怎樣的一種職業?你遇到過哪些有趣的修表師傅?
    有哪些偽傳統?
    鑒定社會畸形的一篇謬論
    中國的「乾爹」文化
    有哪些人如其名的人?

    TAG:文化 | 國際 | 國際化 | 編碼 | URL編碼 | 解析 | 權威 | 問題 | 中文 |