一、問題:
編碼問題是JAVA初學(xué)者在web開發(fā)過程中經(jīng)常會遇到問題,網(wǎng)上也有大量相關(guān)的文章介紹,但其中很多文章并沒有對URL中使用了中文等非ASCII的字符造成服務(wù)器后臺程序解析出現(xiàn)亂碼的問題作出準(zhǔn)確的解釋和說明。本文將詳細(xì)介紹由于在URL中使用了中文等非ASCII的字符造成亂碼的問題。
1、在URL中中文字符通常出現(xiàn)在以下兩個地方:
(1)、Query String中的參數(shù)值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords=中國
(2)、servlet path,比如:http://search.china.alibaba.com/selloffer/中國.html
2、出現(xiàn)亂碼問題的原因主要是以下幾方面:
(1)、瀏覽器:我們的客戶端(瀏覽器)本身并沒有遵循URI編碼的規(guī)范(http://www.w3.org/International/O-URL-code.html)。
(2)、Servlet服務(wù)器:Servlet服務(wù)器的沒有正確配置。
(3)、開發(fā)人員并不了解Servlet的規(guī)范和API的含義。
二、基礎(chǔ)知識:
1、一個http請求經(jīng)過的幾個環(huán)節(jié):
瀏覽器(ie firefox)【get/post】------------>Servlet服務(wù)器------------------------------->瀏覽器顯示
編碼 解碼成unicode,然后將顯示的內(nèi)容編碼 解碼
(1) 瀏覽器把URL(以及post提交的內(nèi)容)經(jīng)過編碼后發(fā)送給服務(wù)器。
(2) 這里的Servlet服務(wù)器實際上指的是由Servlet服務(wù)器提供的servlet實現(xiàn)ServletRequestWrapper,不同應(yīng)用服務(wù)器的servlet實現(xiàn)不同,這些servlet的實現(xiàn)把這些內(nèi)容解碼轉(zhuǎn)換為unicode,處理完畢后,然后再把結(jié)果(即網(wǎng)頁)編碼返回給瀏覽器。
(3) 瀏覽器按照指定的編碼顯示該網(wǎng)頁。
當(dāng)對字符串進(jìn)行編碼和解碼的時候都涉及到字符集,通常使用的字符集為ISO8859-1、GBK、UTF-8、UNICODE。
2、URL的組成:
域名:端口/contextPath/servletPath/pathInfo?queryString
說明:
1、ContextPath是在Servlet服務(wù)器的配置文件中指定的。
對于weblogic:
contextPath是在應(yīng)用的weblogic.xml中配置。
<context-root>/</context-root>
對于tomcat:
contextPath是在server.xml中配置。
<Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true" crossContext="true"/>
對于jboos:
contextPath是在應(yīng)用的jboss-web.xml中配置。
<jboss-web>
<context-root>/</context-root>
</jboss-web>
2、ServletPath是在應(yīng)用的web.xml中配置。
<servlet-mapping>
<servlet-name>Example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
2、Servlet API
我們使用以下servlet API獲得URL的值及參數(shù)。
request.getParameter("name"); // 獲得queryString的參數(shù)值(來自于get和post),其值經(jīng)過Servlet服務(wù)器URL Decode過的
request.getPathInfo(); // 注意:pathinfo返回的字符串是經(jīng)過Servlet服務(wù)器URL Decode過的。
requestURI = request.getRequestURI(); // 內(nèi)容為:contextPath/servletPath/pathinfo 瀏覽器提交過來的原始數(shù)據(jù),未被Servlet服務(wù)器URL Decode過。
3、開發(fā)人員必須清楚的servlet規(guī)范:
(1) HttpServletRequest.setCharacterEncoding()方法 僅僅只適用于設(shè)置post提交的request body的編碼而不是設(shè)置get方法提交的queryString的編碼。該方法告訴應(yīng)用服務(wù)器應(yīng)該采用什么編碼解析post傳過來的內(nèi)容。很多文章并沒有說明這一點。
(2) HttpServletRequest.getPathInfo()返回的結(jié)果是由Servlet服務(wù)器解碼(decode)過的。
(3) HttpServletRequest.getRequestURI()返回的字符串沒有被Servlet服務(wù)器decoded過。
(4) POST提交的數(shù)據(jù)是作為request body的一部分。
(5) 網(wǎng)頁的Http頭中ContentType("text/html; charset=GBK")的作用:
(a) 告訴瀏覽器網(wǎng)頁中數(shù)據(jù)是什么編碼;
(b) 表單提交時,通常瀏覽器會根據(jù)ContentType指定的charset對表單中的數(shù)據(jù)編碼,然后發(fā)送給服務(wù)器的。
這里需要注意的是:這里所說的ContentType是指http頭的ContentType,而不是在網(wǎng)頁中meta中的ContentType。
三、下面我們分別從瀏覽器和應(yīng)用服務(wù)器來舉例說明:
URL:http://localhost:8080/example/中國?name=中國
漢字 編碼 二進(jìn)制表示
中國 UTF-8 0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]
中國 GBK 0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]
中國 ISO8859-1 0x3f,0x3f[63, 63]信息失去
(一)、瀏覽器
1、GET方式提交,瀏覽器會對URL進(jìn)行URL encode,然后發(fā)送給服務(wù)器。
(1) 對于中文IE,如果在高級選項中選中總以UTF-8發(fā)送(默認(rèn)方式),則PathInfo是URL Encode是按照UTF-8編碼,QueryString是按照GBK編碼。
http://localhost:8080/example/中國?name=中國
實際上提交是:
GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA
(1) 對于中文IE,如果在高級選項中取消總以UTF-8發(fā)送,則PathInfo和QueryString是URL encode按照GBK編碼。
實際上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
(3) 對于中文firefox,則pathInfo和queryString都是URL encode按照GBK編碼。
實際上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
很顯然,不同的瀏覽器以及同一瀏覽器的不同設(shè)置,會影響最終URL中PathInfo的編碼。對于中文的IE和FIREFOX都是采用GBK編碼QueryString。
小結(jié):解決方案:
1、URL中如果含有中文等非ASCII字符,則瀏覽器會對它們進(jìn)行URLEncode。為了避免瀏覽器采用了我們不希望的編碼,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode編碼過的字符串%.
比如:
URL:http://localhost:8080/example/中國?name=中國
建議:
URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA
2、我們建議URL中PathInfo和QueryString采用相同的編碼,這樣對服務(wù)器端處理的時候會更加簡單。
2、還有一個問題,我發(fā)現(xiàn)很多程序員并不明白URL Encode是需要指定字符集的。不明白的人可以看看這篇文檔:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
2、 POST提交
對于POST方式,表單中的參數(shù)值對是通過request body發(fā)送給服務(wù)器,此時瀏覽器會根據(jù)網(wǎng)頁的ContentType("text/html; charset=GBK")中指定的編碼進(jìn)行對表單中的數(shù)據(jù)進(jìn)行編碼,然后發(fā)給服務(wù)器。
在服務(wù)器端的程序中我們可以通過Request.setCharacterEncoding() 設(shè)置編碼,然后通過request.getParameter獲得正確的數(shù)據(jù)。
解決方案:
1、從最簡單,所需代價最小來看,我們對URL以及網(wǎng)頁中的編碼使用統(tǒng)一的編碼對我們來說是比較合適的。
如果不使用統(tǒng)一編碼的話,我們就需要在程序中做一些編碼轉(zhuǎn)換的事情。這也是我們?yōu)槭裁纯吹接芯W(wǎng)絡(luò)上大量的資料介紹如何對亂碼進(jìn)行處理,其中很多解決方案都只是一時的權(quán)宜之計,沒有從根本上解決問題。
(二)、Servlet服務(wù)器
Servlet服務(wù)器實現(xiàn)的Servlet遇到URL和POST提交的數(shù)據(jù)中含有%的字符串,它會按照指定的字符集解碼。下面兩個Servlet方法返回的結(jié)果都是經(jīng)過解碼的:
request.getParameter("name");
request.getPathInfo();
這里所說的"指定的字符集"是在應(yīng)用服務(wù)器的配置文件中配置。
(1) tomcat服務(wù)器
對于tomcat服務(wù)器,該文件是server.xml
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" URIEncoding="GBK"/>
URIEncoding告訴服務(wù)器servlet解碼URL時采用的編碼。
<Connector port="8080" ... useBodyEncodingForURI="true" />
useBodyEncodingForURI告訴服務(wù)器解碼URL時候需要采用request body指定的編碼。
(2) weblogic服務(wù)器
對于weblogic服務(wù)器,該文件是weblogic.xml
<input-charset>
<java-charset-name>GBK</java-charset-name>
</input-charset>
(三)瀏覽器顯示
瀏覽器根據(jù)http頭中的ContentType("text/html; charset=GBK"),指定的字符集來解碼服務(wù)器發(fā)送過來的字節(jié)流。我們可以調(diào)用HttpServletResponse.setContentType()設(shè)置http頭的ContentType。
總結(jié):
1、URL中的PathInfo和QueryString字符串的編碼和解碼是由瀏覽器和應(yīng)用服務(wù)器的配置決定的,我們的程序不能設(shè)置,不要期望用request.setCharacterEncoding()方法能設(shè)置URL中參數(shù)值解碼時的字符集。
所以我們建議URL中不要使用中文等非ASCII字符,如果含有非ASCII字符的話要使用URLEncode編碼一下,比如:
http://localhost:8080/example1/example/中國
正確的寫法:
http://localhost:8080/example1/example/%E4%B8%AD%E5%9B%BD
并且我們建議URL中不要在PathInfo和QueryString同時使用非ASCII字符,比如
http://localhost:8080/example1/example/中國?name=中國
原因很簡單:不同瀏覽器對URL中PathInfo和QueryString編碼時采用的字符集不同,但應(yīng)用服務(wù)器對URL通常會采用相同的字符集來解碼。
2、我們建議URL中的URL Encode編碼的字符集和網(wǎng)頁的contentType的字符集采用相同的字符集,這樣程序的實現(xiàn)就很簡單,不用做復(fù)雜的編碼轉(zhuǎn)換。
java.lang.Objectjava.net.URLEncoder
public class URLEncoder
HTML 格式編碼的實用工具類。該類包含了將 String 轉(zhuǎn)換為 application/x-www-form-urlencoded
MIME 格式的靜態(tài)方法。有關(guān) HTML 格式編碼的更多信息,請參閱 HTML 規(guī)范。
對 String 編碼時,使用以下規(guī)則:
a
" 到 "z
"、"A
" 到 "Z
" 和 "0
" 到 "9
" 保持不變。 .
"、"-
"、"*
" 和 "_
" 保持不變。
" 轉(zhuǎn)換為一個加號 "+
"。 %xy
" 表示,其中 xy 為該字節(jié)的兩位十六進(jìn)制表示形式。推薦的編碼機(jī)制是 UTF-8。但是,出于兼容性考慮,如果未指定一種編碼,則使用相應(yīng)平臺的默認(rèn)編碼。 例如,使用 UTF-8 編碼機(jī)制,字符串 "The string ü@foo-bar" 將轉(zhuǎn)換為 "The+string+%C3%BC%40foo-bar",因為在 UTF-8 中,字符 ü 編碼為兩個字節(jié),C3 (十六進(jìn)制)和 BC (十六進(jìn)制),字符 @ 編碼為一個字節(jié) 40 (十六進(jìn)制)。