身為非西歐語系的國家,總是得處理編碼的問題,例如,你的使用者會發送中文,那你要如何正確處理請求參數,才可以得到正確的中文字元呢?在〈URL 編碼〉曾經談過 URL 編碼的問題,這是正確處理請求參數前必須知道的基礎。
請求參數的編碼處理,基本上必須分 POST 與 GET 的情況來說明,先來看 POST 的情況…
POST 請求
如果客戶端沒有在 Content-Type
標頭中設定字元編碼資訊(例如瀏覽器可以設定 Content-Type: text/html; charset=UTF-8
),此時使用 HttpServletRequest
的 getCharacterEncoding()
傳回值會是 null
,在這個情況下,容器若使用的預設編碼處理是 ISO-8859-1(大部份瀏覽器預設的字元集),而\客戶端使用 UTF-8 發送非ASCII字元的請求參數,而 Servlet 直接使用 getParameter()
等 取得該請求參數值,就會是不正確的結果也就是得到亂碼。
可以用另一種方式,來簡略表示出為何這個過程會出現亂碼,假設網頁編碼是 UTF-8,透過表單使用 POST 發出「林」這個中文字元,會將「林」作URL編碼為 %E6%9E%97
再送出,也就是瀏覽器相當於作了這個動作:
String text = java.net.URLEncoder.encode("林", "UTF-8");
在 Servlet 取得請求參數時,容器若預設使用 ISO-8859-1 來處理編碼,相當於作了這個動作:
String text = java.net.URLDecoder.decode("%E6%9E%97", "ISO-8859-1");
這樣作的話,顯示出來的中文字元就不正確了。
可以使用 HttpServletRequest
的 setCharacterEncoding()
方法指定取得 POST 請求參數時使用的編 碼。例如若瀏覽器以 UTF-8 來發送請求,接收時也要使用 UTF-8 編碼字串,則可以在取得任何請求值之「前」,執行以下陳述:
request.setCharacterEncoding("UTF-8");
這相當於要求容器作這個動作:
String text = java.net.URLDecoder.decode("%E6%9E%97", "UTF-8");
如此就可以取得正確的 「林」中文字元了。記得,一定要在取得任何請求參數前執行 setCharacterEncoding()
方法才有作用,在取得請求參數之後呼叫 setCharacterEncoding()
沒有任何作用。
GET 請求
在 HttpServletRequest
的 API 文件中,對 setCharacterEncoding()
的說明清楚提到:
Overrides the name of the character encoding used in the body of this request.
也就是說,這個方法對於請求本體中的字元編碼才有作用,也就是基本上這個方法只對 POST 產生作用,當請求是用 GET 發送時,沒有定義這個方法是否會影響 Web 容器處理編碼的方式(究其原因,是因為處理 URL 的是 HTTP 伺服器,而非 Web 容器)。
例如 Tomcat 在 GET 時,使用 setCharacterEncoding()
方法設定編碼就不會有作用。
一個處理字串編碼的方式,是透過 String
的 getBytes()
指定編碼來取得該字串的位元組陣列,然後再重新建構為正確編碼的字串。
例如若瀏覽器使用 UTF-8 處理字元,HTTP 伺服器在 URI 上採用 ISO-8859-1
編碼,正確處理編碼的方式為:
String name = req.getParameter("name");
String name = new String(name.getBytes("ISO-8859-1"), "UTF-8");
舉例來說,在 UTF-8 的網頁中,對「林」這個字元,若使用表單發送 GET 請求,瀏覽器相當於作了這個動作:
String text = java.net.URLEncoder.encode("林", "UTF-8");
HTTP 伺服器若在 URI 採用 ISO-8859-1 編碼,相當於作了這個動作:
String text = java.net.URLDecoder.decode("%E6%9E%97", "ISO-8859-1");
使用 getParameter()
取得的字串就是上例 text
參考的字串,可以依下面的編碼轉換來得到正確的「林」字元:
text = new String(name.getBytes("ISO-8859-1"), "UTF-8");
以上是有關於取得中文請求參數,接下來看如何輸出中文內容。
回應
在沒有設定任何內容型態或編碼之前,HttpServletResponse
使用的字元編碼預設是 ISO-8859-1,也就是說,如果直接輸出中文,在瀏覽器上就會看到亂碼。有幾個方式可以影響 HttpServletResponse
輸出的編碼處理。
瀏覽器如果有發送 Accept-Language
標頭,可以使用 HttpServletRequest
的 getLocale()
來取得一個 Locale
物件,代表客戶端可接受的語系。
可以使用 HttpServletResponse
的 setLocale()
來設定地區(Locale)資訊,地區資訊就包括了語系與編碼資訊。語系資訊通常透過回應標頭 Content-Language
來設定,而 setLocale()
也會設定 HTTP 回應的 Content-Language
標頭。例如:
response.setLocale(Locale.TAIWAN);
這會將 HTTP 回應的 Content-Language 設定為 zh-TW,而字元編碼處理設定為 BIG5。可以使用 HttpServletResponse
的 getCharacterEncoding()
方法取得編碼設定。
可以在web.xml中設定預設的區域與編碼對應。例如:
...
<locale-encoding-mapping-list>
<locale-encoding-mapping>
<locale>zh_TW</locale>
<encoding>UTF-8</encoding>
</locale-encoding-mapping>
</locale-encoding-mapping-list>
...
設定好以上資訊後,若使用 response.setLocale(Locale.TAIWAN)
,或者是 response.setLocale(new Locale("zh", "TW"))
,那麼 HttpServletResponse
的字元編碼處理就採 UTF-8,getCharacterEncoding()
取得的結果就是 "UTF-8"
。
也可以呼叫 HttpServletResponse
的 setCharacgerEncoding()
設定字元編碼:
response.setCharacterEncoding("UTF-8");
或者是在使用 HttpServletResponse
的 setContentType()
時,指定 charset
,charset
的值會自動用來呼叫 setCharacterEncoding()
。
例如以下不僅設定內容類型為 text/html
,也會自動呼叫 setCharacterEncoding()
,設定編碼為 "UTF-8"
:
response.setContentType("text/html; charset=UTF-8");
如果使用了 setCharacterEncoding()
或 setContentType()
時指定了 charset
,則 setLocale()
就會被忽略。
就結論來說,如果要接收中文請求參數並在回應時於瀏覽器正確顯示中文,必須同時設定 HttpServletRequest
的 setCharacterEncoding()
以及 HttpServletResponse
的 setCharacterEncoding()
或 setContentType()
為正確的編碼。
Tomcat 可以在 server.xml 中設定 <Connector URIEncoding="utf-8"...>
,決定 URI 的編碼處理,以前版本的設定是 <Connector URIEncoding="iso-8859-1"...>
,因此過去處理 GET 的中文亂碼時,可以 new String(param.getBytes("ISO-8859-1"), "your encoding")
來解決,不過,如果 server.xml 中設定 <Connector URIEncoding="utf-8"...>
就不適用了(像是 Tomcat 8 之後),例如 GET 請求時的請求參數為 Big5,而 URI 編碼採用 UTF-8,可能會沒有對應編碼,也就是顯示時會是「?」,這樣通常就沒救了 …
如果以前就是用 new String(param.getBytes("ISO-8859-1"), "your encoding")
來處理 GET 時的亂碼,在新版本 Tomcat 上,將 <Connector URIEncoding="utf-8"...>
改回 <Connector URIEncoding="iso-8859-1"...>
就可以了…當然,這是暫時之計,UTF-8 目前是主流,還是讓應用程式支援 UTF-8 為上策。