Java編碼(三)——Java網路I/O(JavaWeb)的編碼解碼過程(接上篇第四)
在JavaWeb中涉及的編碼解碼的方面:
使用者想伺服器傳送一個HTTP請求,需要編碼的地方有url、cookie、parameter,經過編碼後伺服器接受HTTP請求,解析HTTP請求,然後對url、cookie、parameter進行解碼。在伺服器進行業務邏輯處理過程中可能需要讀取資料庫、磁碟檔案或者網路中的其他檔案等等,這些過程都需要進行編碼解碼。當處理完成後,伺服器將資料進行編碼後傳送給客戶端,瀏覽器經過解碼後顯示給使用者。
總結來講,JavaWeb涉及的編碼方面概括來講有:
- URL請求
- Cookie請求與響應
- 表單Get與Post
- 資料庫互動
- 磁碟檔案互動(見上)
一、URL編碼與解碼
對於URL,如果該URL中全部都是英文的沒有什麼問題,如果有中文就要涉及到編碼了。
URL的組成部分:
1)瀏覽器將會對path和parameter進行編碼操作。
將以上地址輸入到瀏覽器URL輸入框中,通過檢視http 報文頭資訊我們可以看到瀏覽器是如何進行編碼的。下面是IE、Firefox、Chrome三個瀏覽器的編碼情況:
可以看到各大瀏覽器對“我是”的編碼情況如下:
path部分 |
Query String |
|
Firefox |
E6 88 91 E6 98 AF |
E6 88 91 E6 98 AF |
Chrome |
E6 88 91 E6 98 AF |
E6 88 91 E6 98 AF |
IE |
E6 88 91 E6 98 AF |
CE D2 CA C7 |
可知對於path部分Firefox、chrome、IE都是採用UTF-8編碼格式,對於Query String部分Firefox、chrome採用UTF-8,IE採用GBK。至於為什麼會加上%,這是因為URL的編碼規範規定瀏覽器將ASCII字元非 ASCII 字元按照某種編碼格式編碼成 16 進位制數字然後將每個 16 進製表示的位元組前加上“%”。
當然對於不同的瀏覽器,相同瀏覽器不同版本,不同的作業系統等環境都會導致編碼結果不同,上表某一種情況,對於URL編碼規則下任何結論都是過早的。由於各大瀏覽器、各個作業系統對URL的URI、QueryString編碼都可能存在不同,這樣對伺服器的解碼勢必會造成很大的困擾。
2)tomcat對URL進行解碼操作的
解析請求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個方法把傳過來的 URL 的 byte[] 設定到 org.apache.coyote.Request 的相應的屬性中。這裡的 URL 仍然是 byte 格式,轉成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:
protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); //獲取URI解碼集 if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }
從上面的程式碼可知,對URI的解碼操作是首先獲取Connector的解碼集,該配置在server.xml中:
<Connector URIEncoding="utf-8" />
如果沒有定義則會採用預設編碼ISO-8859-1來解析。
對於Query String部分,我們知道無論我們是通過get方式還是POST方式提交,所有的引數都是儲存在Parameters,然後我們通過request.getParameter,解碼工作就是在第一次呼叫getParameter方法時進行的。在getParameter方法內部它呼叫org.apache.catalina.connector.Request 的 parseParameters 方法,這個方法將會對傳遞的引數進行解碼。下面程式碼只是parseParameters方法的一部分:
//獲取編碼 String enc = getCharacterEncoding(); //獲取ContentType 中定義的 Charset boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); if (enc != null) { //如果設定編碼不為空,則設定編碼為enc parameters.setEncoding(enc); if (useBodyEncodingForURI) { //如果設定了Chartset,則設定queryString的解碼為ChartSet parameters.setQueryStringEncoding(enc); } } else { //設定預設解碼方式 parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); if (useBodyEncodingForURI) { parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); } }
從上面程式碼可以看出對query String的解碼格式要麼採用設定的ChartSet要麼採用預設的解碼格式ISO-8859-1。注意這個設定的ChartSet是在 http Header中定義的ContentType,同時如果我們需要改指定屬性生效,還需要進行如下配置:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
二、Cookie的編碼與解碼
URLDecoder.decode();
URLEncoder.encode()
三、Get與Post的編碼與解碼
1)Get
當用戶點選submit提交表單時,瀏覽器會用設定的編碼(contentType)來編碼資料傳遞給伺服器,通過GET方式提交的資料都是拼接在URL後面來提交的,所以tomcat伺服器在進行解碼過程中URIEncoding就起到作用了。tomcat伺服器會根據設定的URIEncoding來進行解碼,如果沒有設定則會使用預設的ISO-8859-1來解碼。假如我們在頁面將編碼設定為UTF-8,而URIEncoding設定的不是或者沒有設定,那麼伺服器進行解碼時就會產生亂碼。這個時候我們一般可以通過new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8") 的形式來獲取正確資料。
2)Post
當我通過點選頁面的submit按鈕來提交表單時,瀏覽器會用設定的編碼(contentType的charset編碼格式)來對POST表單的引數進行編碼然後提交給伺服器,在伺服器端同樣也是用contentType中設定的字符集來進行解碼(這裡與get方式就不同了),這就是通過POST表單提交的引數一般而言都不會出現亂碼問題。當然這個字符集編碼我們是可以自己設定的:request.setCharacterEncoding(charset) 、response.setCharacterEncoding(charset) 或 contentType="text/html;charset=UTF-8"。
四、資料庫互動的編碼與解碼
Java程式與資料庫的連線都是通過JDBC驅動程式來連線的,而JDBC驅動程式預設的是ISO-8859-1編碼格式的,也就是說我們通過java程式向資料庫傳遞資料時,JDBC首先會將Unicode編碼格式的資料轉換為ISO-8859-1的編碼格式,然後在儲存在資料庫中,即在資料庫儲存資料時,預設格式為ISO-8859-1。
故可以通過jdbcurl新增:
useUnicode=true&characterEncoding=utf8
(另提:資料庫本身的儲存編碼則是通過my.ini來配置的,將預設的latin該成utf8,mysql為例)
五、JSP與Servlet的編碼與解碼過程
1)JSP
我們知道JSP頁面是需要轉換為servlet的,在轉換過程中肯定是要進行編碼的。在JSP轉換為servlet過程中下面一段程式碼起到至關重要的作用。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>
在上面程式碼中有兩個地方存在編碼:pageEncoding、contentType的charset。其中pageEncoding是jsp檔案本身的編碼,即經過JSP容器轉換為servlet用的編碼,而contentType的charset是指伺服器傳送給客戶端時的內容編碼。
JSP的轉換過程:
第一階段
JVM將JSP編譯為.jsp檔案。在這個過程中pageEncoding就起到作用了,JVM首先會獲取pageEncoding的值,如果該值存在則採用它設定的編碼來編譯,否則則採用file.encoding編碼來編譯。
第二階段
JVM將.java檔案轉換為.class檔案。在這個過程就與任何編碼的設定都沒有關係了,不管JSP採用了什麼樣的編碼格式都將無效。經過這個階段後.jsp檔案就轉換成了統一的Unicode格式的.class檔案了。
第三階段
後臺經過業務邏輯處理後將產生的結果輸出到客戶端。在這個過程中contentType的charset就發揮了功效。如果設定了charset則瀏覽器就會使用指定的編碼格式進行解碼,否則採用預設的ISO-8859-1編碼格式進行解碼處理。
流程如如下:
2)Servlet
當用戶請求Servlet時,WEB容器會呼叫它的JVM來執行Servlet。首先JVM會把servlet的class載入到記憶體中去,記憶體中的servlet程式碼是Unicode編碼格式的。然後JVM在記憶體中執行該Servlet,在執行過程中如果需要接受從客戶端傳遞過來的資料(如表單和URL傳遞的資料),則WEB容器會接受傳入的資料,在接收過程中如果程式設定了傳入引數的的編碼則採用設定的編碼格式(通過tomcat配置),如果沒有設定則採用預設的ISO-8859-1編碼格式,接收的資料後JVM會將這些資料進行編碼格式轉換為Unicode並且存入到記憶體中。執行Servlet後產生輸出結果,同時這些輸出結果的編碼格式仍然為Unicode。緊接著WEB容器會將產生的Unicode編碼格式的字串直接傳送置客戶端,如果程式指定了輸出時的編碼格式,則按照指定的編碼格式輸出到瀏覽器,否則採用預設的ISO-8859-1編碼格式。整個過程流程圖如下:
六、總結
總結在javaweb中出現的所有編碼問題的解決方法:
1)URL編碼亂碼:
在Tomcat中,預設情況下使用ISO- 8859-1對URL提交的資料和表單中GET方式提交的資料進行重新編碼(解碼),而不使用該引數對URL提交的資料和表單中GET方式提交的資料進行重新編碼(解碼)。要解決該問題,應該在Tomcat的配置檔案的Connector標籤中設定useBodyEncodingForURI或者 URIEncoding屬性。
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
2)Cookie存取編碼亂碼
URLDecoder.decode()
URLEncoder.encode()
3)Get編碼亂碼
與URL一致
4)Post編碼亂碼
request.setCharacterEncoding("UTF-8") //作用是設定對客戶端請求進行重新編碼的編碼。 response.setCharacterEncoding("UTF-8") //作用是指定對伺服器響應進行重新編碼的編碼。
5)資料庫互動編碼亂碼
useUnicode=true&characterEncoding=utf8
6)資料庫內部資料亂碼
my.ini的latin改成utf8
7)JSP頁面與Servlet響應編碼亂碼
pageEncoding="UTF-8" //作用是設定JSP編譯成Servlet時使用的編碼。
contentType="text/html;charset=UTF-8" //作用是指定對伺服器響應進行重新編碼的編碼。