概述
開發Web項目的過程中,經常遇到瀏覽器中顯示的內容亂碼,或者服務器獲取瀏覽器請求參數時亂碼的問題,很多同學基本都是在遇到亂碼的時候去網上一頓搜索,然后看哪篇文章比較靠譜就照着上面的內容去配后亂碼成功消失了,然后就沒然后了...
最后基本只是停留在知道怎么樣設置能避免常見的亂碼問題,而不知道具體的原理,一旦遇到了網上查不到的亂碼場景就不知道如何解決了~
本文會深入的讓你了解針對於HTTP請求時,這一去一回(Request,Response)之間,到底做了怎樣的事情,讓你徹底告別Web項目中的亂碼煩惱。本文的內容是基於Tomcat 8.0.23版本的,其他容器也可以參考本文的內容,畢竟理論都是通的~
Request亂碼
在Request過程中我們需要注意2個步驟,第一個是請求發送時所使用的編碼,第二個是應用收到請求后解碼時所用的編碼,只有保證這兩步中使用相同的編碼即可有效防止亂碼的發生。那么請求時使用的是什么編碼呢?這個主要取決於請求時的客戶端。
下面我們做個測試,客戶端分別使用瀏覽器和curl來請求,服務端使用Tomcat 8.0.23來處理。
請求地址:http://localhost:8080/ccj/楚楚街?query=買的漂亮
我們先來分析下上面的地址:
url<url>:http://localhost:8080/ccj/楚楚街?query=買的漂亮
uri<path>:/ccj/楚楚街
queryString<query>:query=買的漂亮
歷史原因上面的名稱表示的並不准確,現修正為<>部分的名稱~
Tomcat使用默認配置,使用下面代碼來接收請求:
Firefox中請求 http://localhost:8080/ccj/楚楚街?query=買的漂亮 結果如下:
上面信息可以看出,在發送請求之前Firefox先對請求地址中的中文使用UTF-8編碼進行了百分號編碼,關於百分號編碼的內容可以自行Google查閱相關內容,這里不在贅述。
這里我們發現獲取的內容居然沒有亂碼,這是因為我是用的是Tomcat8的緣故,看下官方說明:
在不指定URIEncoding的情況下,並且也沒指定系統屬性org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true時,默認使用UTF-8進行解碼。這個變化是從Tomcat8開始的,Tomcat8之前的的處理方式是,如果沒指定
URIEncoding的情況下,那么會直接使用ISO-8859-1作為默認編碼的。
下面我們加上系統屬性org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true來模擬Tomcat8之前的情況,發起同樣的請求 http://localhost:8080/ccj/楚楚街?query=買的漂亮 結果如下:
看結果發現,果然出現了亂碼問題,那么我們已經知道了Firefox發送過來的請求是經過了UTF-8編碼的百分號編碼,那么我們可以對query進行ISO-8859-1編碼的百分號解碼來重現下亂碼,看結果:
我靠~什么鬼為什么亂碼跟我們預想的結果不一樣,這個亂碼跟之前訪問Sevlet中出現的亂碼不一樣???難道之前的結論都是錯的么???其實並不是~這里出現2次亂碼顯示的不一致的情況是控制台編碼不一致導致的,
我是用的IDEA,IDEA的控制台編碼是受到-Dfile.encoding這個虛擬機啟動參數影響的,這里送上之前寫的一篇文章供參考(Java虛擬機(JVM)默認字符集詳解)。
================================================華麗的分割線之IDEA亂碼問題開始================================================
既然遇到了控制台編碼亂碼問題,接下來我就順便講一下IDEA控制台亂碼的解決方法吧~首先我們看上面2附圖,先看第一個圖請求Servlet時控制台打印的是 file.encoding=GBK,而第二個圖中使用main方法運行后
控制台打印的是 file.encoding=UTF-8。2次運行使用的都是同一個IDEA實例,為什么編碼會不一樣呢?我們先關掉IDEA,然后殺死所有當前java進程,然后重新啟動IDEA,看一下進程啟動參數,因為是windows系統,所以cmd中使用命令:
wmic process where caption="java.exe" get caption,commandline /value
Linux下使用命令:ps -aux | grep java
結果如下圖:
看見了吧,-Dfile.encoding=GBK這個是當前IDEA實例啟動時候的JVM虛擬參數,所以當前IDEA的控制台編碼是GBK,但為什么我們跑main方法時打印的信息是-Dfile.encoding=UTF-8呢,這是因為我修改了Settings
中的File Encodings導致的,直接看圖:
這個地方的配置會影響運行main方法時的控制台編碼,那么既然知道了原因,我們就開始解決問題吧~首先我們需要更改IDEA啟動時的參數-Dfile.encoding=UTF-8,修改的方式為:
windows x64環境修改 C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2017.1\bin\idea64.exe.vmoptions
windows x86環境修改 C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2017.1\bin\idea.exe.vmoptions
分不清楚的可以將上面2個文件全部修改~修改內容為在文件末尾加上紅框中的內容:
MacOS系統修改文件 /Applications/IntelliJ IDEA 2017.app/Contents/Info.plist,修改完之后重啟IDEA才能生效~
重啟后我們再次訪問 http://localhost:8080/ccj/楚楚街?query=買的漂亮 結果如下:
結果發現依然是亂碼,並且亂碼跟最開始訪問的時候不一樣了...這是什么鬼...我們繼續來分析,因為是運行在Tomcat中,所以我們在打印的時候會根據啟動Tomcat的那個JVM的file.encoding進行編碼,
通過查看線程啟動參數發現Tomcat啟動時的file.encoding=GBK,那么問題就迎刃而解了,原理簡述如下:
1.System.out.println("query: " + query);時,首先根據啟動Tomcat的那個JVM的file.encoding進行編碼(也就是GBK),然后將字節流傳給控制台。
2.控制台收到字節流,然后根據啟動IDEA的那個JVM的file.encoding進行解碼(也就是UTF-8),然后顯示在控制台。
由於上面的編碼和解碼的不同,導致了最終的亂碼,針對上面的結論我們可以使用main方法來模擬下,如下圖:
看見了吧,亂碼跟之前的亂碼相同了~到此原理已經懂了,那么就直接解決問題吧,給Tomcat的啟動參數加上-Dfile.encoding=utf-8后,再次訪問 http://localhost:8080/ccj/楚楚街?query=買的漂亮 結果如下:
這次OK了,亂碼的內容跟我們在main方法里面模擬的內容一致了~至此,由於IDEA引起的亂碼問題解決了,我們繼續討論Request的亂碼問題~
================================================華麗的分割線之IDEA亂碼問題結束================================================
根據上面的分析后,我們知道了Request亂碼產生的原因,就是因為請求發送時的編碼和接收請求時解碼時的編碼不一致導致的,那么我們需要做的就是保證他們都使用相同的編碼即可。
總結:
解決Request請求地址中中文亂碼問題如下:
1.Tomcat8以及之后,我們只需要使用Tomcat默認的配置即可在收到請求時默認使用UTF-8編碼進行解碼。
2.Tomcat8之前,我們需要修改 C:\Program Files\apache-tomcat-8.0.23\conf\server.xml 文件,參考下圖:
在圖中位置,添加紅框中的內容即可~
到此為止,Request請求地址中中文亂碼問題解決了~接下來我們來看看Request的body中的亂碼問題。
我們直接將剛才的GET請求 http://localhost:8080/ccj/楚楚街?query=買的漂亮 轉換成POST請求 http://localhost:8080/ccj/楚楚街 並將請求參數(query=買的漂亮)放入請求體中,如下圖:
然后我們看下Servlet中打印的結果:
發現獲取參數query的時候依然是亂碼,此時我們只需要在所有Request的訪問之前設置一下req.setCharacterEncoding("UTF-8");即可,設置重新訪問結果如下:
亂碼成功解決,至此瀏覽器發起Request請求亂碼問題解決~
下面我們在看看通過工具發起Request的情況會是什么樣~
我們這里使用Fiddler進行模擬GET請求:
然后我們看控制台結果:
結果發現,居然又亂碼了...這次的原因是,因為我們使用的是工具而不是瀏覽器,瀏覽器是遵循HTTP規范的,而工具則不會,導致本次測試最后亂碼的根本原因就是沒有進行百分號編碼。
這里簡單說下原理:
1.Fiddler在請求的時候對請求地址做了UTF-8編碼,拿query=買的漂亮來舉例,在發送Request之前會對URL進行編碼(這里是UTF-8)為字節流后發送給服務端,因為Fiddler沒有做百分號編碼,所以在對URL進行編碼發送的時候,
URL中的query內容就是 買的漂亮 這四個漢字,買的漂亮 進行UTF-8編碼后為e4b9b0(買)e79a84(的)e6bc82(漂)e4baae(亮)。
2.服務端在收到Request后,會將URL地址的字節流存入ByteChunk中,而我們只有在獲取url、uri、queryString的時候出現了亂碼的情況,這是因為在獲取這3個值的時候,是不會對字節流進行解碼操作的,而ByteChunk中有個默認
的Charset(即ISO-8859-1),在不進行解碼的情況下(即沒顯式的調用ByteChunk中的setCharset(Charset charset)方法的時候),默認會使用這個默認值ISO-8859-1對獲取的字節流直接解碼,因此在解碼
e4b9b0(買)e79a84(的)e6bc82(漂)e4baae(亮)的時候就亂碼了,因為ISO-8859-1無法表示漢字。
3.所以對URL進行百分號編碼是非常有必要的~
如果對上面的e4b9b0(買)e79a84(的)e6bc82(漂)e4baae(亮)進行百分號編碼的話, 那么在到達URL字節流編碼這一步之前,URL中的 買的漂亮 就會被替換為 "%E4%B9%B0%E7%9A%84%E6%BC%82%E4%BA%AE",
之后URL進行字節流編碼時,是基於字符串"%E4%B9%B0%E7%9A%84%E6%BC%82%E4%BA%AE"進行的,因為里面都是ISO-8859-1表示范圍內的字符,所以在服務端對百分號編碼后的字節流進行直接獲取的時候(也就是不對字節流
進行解碼,而使用ByteChunk中默認的編碼ISO-8859-1進行解碼),就不會出現亂碼了,其返回的內容就是百分號編碼后的內容~
瀏覽器直接請求:http://localhost:8080/ccj/楚楚街?query=買的漂亮 結果如下:
至此,Request亂碼問題全部解決~
總結:
解決Request請求地址中中文亂碼問題如下:
1.Tomcat8以及之后,我們只需要使用Tomcat默認的配置即可在收到請求時默認使用UTF-8編碼進行解碼。
2.Tomcat8之前,我們需要修改 C:\Program Files\apache-tomcat-8.0.23\conf\server.xml 文件,在Connector中添加URIEncoding="UTF-8"。
解決Request請求體中的中文亂碼問題如下:
1.在所有Request的訪問之前設置一下req.setCharacterEncoding("UTF-8");即可。
解決使用工具請求導致對原始字節流進行獲取時的亂碼問題(如:req.getRequestURL()、req.getRequestURI()、req.getQueryString()等方法):
1.對URL進行指定字符集的百分號編碼即可。
寫了大半天時間...希望能對遇到亂碼的同學有所幫助~其實我也挺懶的...很久之前就想寫這篇文章了一直拖到現在才寫~o(∩_∩)o ~
下一篇內容會針對Resopnse的亂碼問題展開講解,這里附上鏈接方便跳轉:一文讓你從此告別HTTP亂碼(二)Response篇