Java Web中的編碼問題(一)前端
注:部份內容來源於網絡java
計算機的底層只認識010101…,這是計算認識的語言。而人類使用語言有漢語、英語、日語等等。人類的語言計算機是不認識的。因此計算機和人若是想交互的話,就須要進行翻譯,預約一種規則,例如:一個漢字「遠」在計算機中用幾個01表示。web
而這個規則就是字符集,一個字符集會提供字符到01之間的映射關係。以下圖數據庫
首先是在I/O中存在的編碼,通常是在字符和字節之間的轉換。通常在I/O操做時使用的編解碼字符集一致的話就不會出現亂碼問題。apache
其次是在內存中存在的編碼。String提供了構造方法將byte[]按指定的字符集解碼成字符串,一級getByte()按自定字符集進行編碼。瀏覽器
UTF-8算是如今用的比較多的字符集,它對漢字採用3個字節表示。而且UTF-8能對單個字符的編值進行校驗,例如一個utf-8編碼的byte[]中若是其中的一個字符被損壞,不會影響其餘字符的碼值。因此,utf-8更適合網絡傳輸。tomcat
從使用中文的角度來將,有I/O的地方就會設計編碼。服務器
數據通過網絡傳輸都是以字節爲單位的,因此全部的數據都是可以被序列化爲字節的。在java中數據要被序列化,比繼續實現Serializable接口。網絡
用戶從瀏覽器發起一個HTTP請求,須要存在編碼的地方是URL、Cookie、Parameter。編碼
服務端接收到HTTP請求後要解析HTTP,其中URL、Cookie和POST表單參數須要解碼,服務器可能須要讀取數據庫中的數據、本地或網絡中其餘地方的文本文件,這些數據均可能存在編碼問題。當Servlet處理完全部的請求的數據後,須要將這些數據再編碼,經過Socket發送到用戶請求的瀏覽器裏,在通過瀏覽器解碼稱爲文本。
用戶提交一個URL,在這個URL中可能存在中文,所以須要編碼。下圖介紹了URL(這裏所說的URL和URL是針對Servlet進行描述的,也就是request.getRequestURL()和request.getRequestURI()返回的URL和URI進行描述的)的幾個組成部分。
以tomcat做爲Servlet Engine爲例,把他們分別對應到下面的這些配置文件中。
Port對應在tomcat的<Connector port=」8080」/>中配置,而Context Path在<Context path=」/examples」/>中配置,Servlet Path在Web應用中的web.xml的<url-pattern>中配置,PathInfo是咱們請求的具體的Servlet,QueryString是要傳遞的參數。注意這裏是在瀏覽器直接輸入URL,因此是以GET方法請求的,若是經過POST方法請求,QueryString將經過表單方式提交到服務器端。
上圖中的ServletPath和QueryString中部分出現了中文,當咱們在瀏覽器中直接輸入這個URL時,在瀏覽器和服務器端時如何編碼和解析這個URL呢?
咱們經過谷歌瀏覽器調試觀察咱們請求的URL的實際內容。
長遠的編碼結果爲E995BF 和 E8BF9C,可知PathInfo是utf-8編碼,QueryString也是utf-8編碼。置於爲何會有「%」,查閱URL的編碼規範RFC3986可知,瀏覽器編碼URL是將非ASCII字符按照某種編碼格式編碼成16禁止數字後將每一個16進製表示的字節前加上「%」,因此最終的URL就成上面的格式。
有的瀏覽器對PathInfo和QueryString的編碼是不同的,不一樣的瀏覽器對PathInfo的編碼也可能不同。
以tomcat爲例看一下,Tomcat接收到這個URL是如何解碼的。
解析請求的URL是在org.apache.coyote.http11.InternalInputBuffer的parseRequestLine方法中進行的,這個方法把傳過來的URL的byte[]設置到org.apache.coyote.Request的相應屬性中。這裏的URL仍然是byte格式,轉成char是在org.apache.catalina.connector.CoyoteAdapter的convertURI方法中。
對URL的URI部分進行解碼的字符集是在connector的<Connector URIEncoding=」UTF-8」>中定義的,若是沒有定義,那麼將以默認編碼ISO-8859-1解析。因此有中文URL是最好把URIEncoding設置成UTF-8編碼。
對於QueryString的解析過程:
以GET方式HTTP請求的QueryString與以POST方式的HTTP請求的表單參數都是做爲Parameters保存的,都經過request.getParameter獲取參數值。對它們的解碼是在request.getParameter方法第一次調用時進行的。
request.getParameter方法被調用時將會調用org.apache.catalina.connector.Request的parseParameters方法。這個方法將會對GET和POST方式傳遞的參數進行解碼。可是它們的解碼字符集有可能不同。QueryString的解碼字符集是在哪裏定義的呢。它自己是經過HTTP的Header傳到服務端的,而且也在URL中。
QueryString的解碼字符集要麼是Header中ContentType定義的Charset,要麼是ISO-8859-1,要使用ContentType中定義的編碼,就要將connector的<Connector URIEncoding=」UTF-8」 useBodyEncodingForURI=」true」/>中的useBodyEncodingForURI設置爲true。這個項的名字容易讓人產生混淆,它並非對整個URI都採用BodyEncoding進行解碼,而僅僅是對QueryString使用BodyEncoding解碼,這一點還要特別注意。
在咱們的應用程序中,應該儘可能避免在URL中使用非ASCII字符,否則極可能會碰到亂碼問題。固然咱們在服務端最好設置<Connecter/>中的URIEncoding和useBodyEncodingForURI連個參數。
咱們在前端經過get請求提交了一個參數包含中文,例如:username=長遠,在服務器這邊經過
request.getParameter獲取時,拿到
????
而後咱們經過
username = new String(username.getBytes(「ISO-8859-1」), 「UTF-8」);
就能拿到正確的中文。
這其中起始發生兩次編解碼(以谷歌瀏覽器爲例):
(1) 第一次編碼,瀏覽器將「長遠」編碼成爲byte[];
(2) 第一次解碼,Tomcat這邊接收到這個byte[],沒有因爲沒有設置QueryString的的字符集,以默認字符集ISO-8859-1進行解碼,也就是把這個byte[]當成ISO-8859-1格式進行解碼。也就是request.getParameter獲取到的結果????;
(3) 第二次編碼,username.getBytes(「ISO-8859-1」)發生了第二次編碼,此時返回的byte[]和瀏覽器發過來的是同樣的,utf-8編碼的;
(4) 第二次解碼,new String(username.getBytes(「ISO-8859-1」), 「UTF-8」),咱們經過(3)拿到了實際編碼格式utf-8的byte[],再用utf-8進行解碼,固然就獲得了正確的結果。
這其中多了一次編解碼,如在實際生產環境中,若是服務器沒有使用正確的字符集去解析前端傳過來的中文,經過這種方式進行轉碼,無疑增長的服務器的負擔。