1. 程式人生 > >採用DoGet方式提交中文,亂碼產生原因分析及解決辦法

採用DoGet方式提交中文,亂碼產生原因分析及解決辦法

前段時間某功能在測試機器上出現亂碼,情況如下: 現象:           除錯搜尋功能時,通過doGet方法提交到後臺的中文引數在本地和開發測試機器上為亂碼(Action層),在測試人員測試機器上為中文.(Action層) 推斷:
懷疑是兩臺機器(開發人員測試機器,測試人員測試機器)環境不同: 1. 先從tomcat查起,在他們各自的tomcat的配置檔案server.xml中的Connector標籤,有句指定URL編碼的配置:URIEncoding="UTF-8" ,     開發測試機器上沒有配置(預設是IDO-8859-1),測試人員測試機器上配置為GBK,兩個配置不同,改成一致(預設),再驗證,還是存在亂碼; 2. 懷疑是系統字符集的問題,用locale命令和檢視/etc/sysconfig/i18n
 檔案檢視兩臺機器的字符集,檢視後發現兩臺機器系統字符集一致,都為UTF-8 3. 懷疑是apache上有不同的設定,再到apache的httpd.conf上檢視,兩天的設定基本一致,沒有對編碼有特殊的設定. 4. 懷疑是JVM執行引數的原因.java有一個執行時引數叫做:file.edcoding.這個編碼是儲存java檔案的編碼字符集,在呼叫javac.exe時,JDK用file.edcoding將.java檔案     讀進來,轉化為UNICODE放到記憶體中,所以,這個引數對亂碼不會產生影響. 從以上可以看出,常規的排查手段已經不能找到原因,所以,從HTTP通訊開始下手,從請求發起開始一步步排查, 首先,在本地,用到了3個工具,分別是:      1. httpFox , Firefox上的一個外掛,用來檢視所有發起的http請求,內容非常詳細.      2. membrane-monitor 也是檢視http通訊的客戶端,不依賴瀏覽器.      3. Wireshark 同樣是http的抓包工具,他不僅僅可以自己通過各種表示式抓取特定http包,還可以解析tcpdump(後面會說到)抓取的資訊(好像tcpdump抓取的是16進位制的資料) 這次排查三種工具都用到,最簡單的是httpFox 使用簡單,該有的功能都有,其次是Wireshark,還有很多功能沒用到,感覺很強大,而且關鍵是和瀏覽器無關,是個很好的分析工具. 排查在兩個瀏覽器中進行,IE9和Firefox17,在Firefox下可以看到用DoGET的方式帶中文引數請求:

 
 Wireshark 抓取到的資訊如下,是UTF-8編碼的資訊,這步是瀏覽器自己做的:
 
 在IE9下可以看到用DoGET的方式帶中文引數請求:
 
 Wireshark 抓取到的資訊如下,是UTF-8編碼的資訊,這步是瀏覽器自己做的:
   從這裡可以看到兩個瀏覽傳送中文引數請求的編碼方式是不同的.請注意IE裡面的高階設定中有這麼一個選項: 經過試驗這的URF8編碼,是對URL中的中文例如: www.baidu.com/中文/index.html 中的中文二字進行編碼
傳送請求後用Wireshark 可以看到,如下圖,編碼後的部分URL


 
為了驗證,我在伺服器上(開發人員測試機器)上又裝上了伺服器端的http抓包工具,tcpdump,安裝過程如下: 1、開啟網址:www.tcpdump.org/ 下載 libpcap-1.0.0.tar.gz (595.0KB) 軟體包,通過命令 tar zxvf libpcap-1.0.0.tar.gz 解壓檔案,並將其放入自定義的安裝目錄。 2、開啟網址:www.tcpdump.org/ 下載tcpdump-4.3.0.tar.gz (867.0KB) 軟體包,通過命令 tar zxvf tcpdump-4.3.0.tar.gz 解壓檔案,並將其放入自定義的安裝目錄。 3.一次到解壓目錄下:      

     ./configure

  make

  make install 即可安裝. 接著使用命令,將抓取到的資訊放到write.log中,  tcpdump -X -s 0  -w write.log   host xxx.xxx.xxx.xxx  and port 80 說明下引數:  -x  以16 進位制數形式顯示每一個報文
 -s  重定義擷取報文大小,預設為96(或68),如果定義為0,則表示獲取完整報文
 -w 將抓取到的報文放到檔案中   
 host 指定抓取報文的目的地址
 port  指定報文目的埠,因為該機器上是apache轉發,填apache的埠就可以.
在服務端執行命令後用IE,Firefox下發送請求,將抓取到的檔案放到Wireshark中解析,可以看到:
Firefox傳送請求時服務端擷取的檔案:


 
 
IE9傳送請求時服務端擷取的檔案:


  可以看到和瀏覽器提交的編碼是一致的,所以,可以推斷,在瀏覽器端是編碼傳輸的,並且沒有產生所謂的亂碼. 然後,我在服務端開啟遠端debug,可以看到在呼叫以下方法的時候產生了亂碼:

  但是仔細檢視,發現request物件中有個specialAttribute屬性,裡面以鍵值對的方式存放了keyword引數,這裡的keyword是上圖中瀏覽器編碼後的值,所以,在這裡是沒有亂碼的。 為什麼在呼叫request.getParameterMap()方法後會產生亂碼呢?  其實原因和tomcat有關係,在我們呼叫getParameterMap方法時,request物件會去找WEB容器中的URL編碼去解碼URL,在Tomcat中指的是在server.xml的以下配置中配置的編碼
 
而開發人員測試機器上沒有改配置,即預設使用ISO-8859-1去解碼,測試人員測試機器上配置為GBK,即用GBK去解碼URL,如果瀏覽器的編碼和tomcat配置檔案中配置的編碼不一致,就會產生亂碼 ,
為了證明我的猜想,我簡單加密解密了一段中文,執行結果如下:


 
這裡與我在debug過程中看到的亂碼是一致,從而得出結論,亂碼的根音來自於tomcat配置檔案中設定的URLEncoding引數。
最後,再次總結下:
     1. 採用DoGet方式傳值,瀏覽器會幫我們編碼,但是各個瀏覽器的編碼方式不一致。
     2. 傳輸過程中,編碼後的值不會再被編碼。
     3. 通過Request物件取引數值時,Request物件會去Web容器中獲取URL的編碼方式,並用同樣的編碼區解碼URL,如果瀏覽器的編碼和Web容器中的編碼不一致,就會產生亂碼。
     4. 解決方法,在瀏覽器提交請求前,將中文引數編碼兩次,瀏覽器不會再對其進行編碼,傳到服務端後解碼一次即可。
為什麼客戶端編碼兩次,服務端解碼一次?請看下圖:

 
先宣告一個被兩次編碼以後的字串,模擬我在瀏覽器端手動編碼的結果,
然後用三種方法對該字串解碼,為什麼用三種? 因為這次解碼是在呼叫request.getParameter時
request物件的解碼服務端tomcat容器的URLEcoding可能是三種中的一種,可以看到無論是
什麼編碼方式的解碼結果都是一樣的,因為這裡的配置可能是不一樣的,所以要考慮到各種編碼方式的可能,這個時候,我們再用程式碼手動解碼一次,就可還原回中文了!
亂碼問題就這麼解決了,雖然需要手動的解碼一次,不過我覺得這個值得,因為亂碼後的情況奇奇怪怪,
若覺得每次接麼複雜,只需在攔截器端都統一解碼就可以,以後不用再擔心這個問題.