在上篇博客中LZ介紹了前面兩種場(chǎng)景(IO、內(nèi)存)中的java編碼解碼操作,其實(shí)在這兩種場(chǎng)景中我們只需要在編碼解碼過程中設(shè)置正確的編碼解碼方式一般而言是不會(huì)出現(xiàn)亂碼的。對(duì)于我們從事java開發(fā)的人而言,其實(shí)最容易也是產(chǎn)生亂碼最多的地方就是web部分。首先我們來(lái)看在javaWeb中有哪些地方存在編碼轉(zhuǎn)換操作。
通過下圖我們可以了解在javaWeb中有哪些地方有轉(zhuǎn)碼:
用戶想服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求,需要編碼的地方有url、cookie、parameter,經(jīng)過編碼后服務(wù)器接受HTTP請(qǐng)求,解析HTTP請(qǐng)求,然后對(duì)url、cookie、parameter進(jìn)行解碼。在服務(wù)器進(jìn)行業(yè)務(wù)邏輯處理過程中可能需要讀取數(shù)據(jù)庫(kù)、本地文件或者網(wǎng)絡(luò)中的其他文件等等,這些過程都需要進(jìn)行編碼解碼。當(dāng)處理完成后,服務(wù)器將數(shù)據(jù)進(jìn)行編碼后發(fā)送給客戶端,瀏覽器經(jīng)過解碼后顯示給用戶。在這個(gè)整個(gè)過程中涉及的編碼解碼的地方較多,其中最容易出現(xiàn)亂碼的位置就在于服務(wù)器與客戶端進(jìn)行交互的過程。
上面整個(gè)過程可以概括成這樣,頁(yè)面編碼數(shù)據(jù)傳遞給服務(wù)器,服務(wù)器對(duì)獲得的數(shù)據(jù)進(jìn)行解碼操作,經(jīng)過一番業(yè)務(wù)邏輯處理后將最終結(jié)果編碼處理后傳遞給客戶端,客戶端解碼展示給用戶。所以下面我就請(qǐng)求對(duì)javaweb的編碼&解碼進(jìn)行闡述。
客戶端想服務(wù)器發(fā)送請(qǐng)求無(wú)非就通過四中情況:
1、URL方式直接訪問。
2、頁(yè)面鏈接。
3、表單get提交
4、表單post提交
對(duì)于URL,如果該URL中全部都是英文的那倒是沒有什么問題,如果有中文就要涉及到編碼了。如何編碼?根據(jù)什么規(guī)則來(lái)編碼?又如何來(lái)解碼呢?下面LZ將一一解答!首先看URL的組成部分:
在這URL中瀏覽器將會(huì)對(duì)path和parameter進(jìn)行編碼操作。為了更好地解釋編碼過程,使用如下URL
http://127.0.0.1:8080/perbank/我是cm?name=我是cm
將以上地址輸入到瀏覽器URL輸入框中,通過查看http 報(bào)文頭信息我們可以看到瀏覽器是如何進(jìn)行編碼的。下面是IE、Firefox、Chrome三個(gè)瀏覽器的編碼情況:
可以看到各大瀏覽器對(duì)“我是”的編碼情況如下:
browser | 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 |
查閱上篇博客的編碼可知對(duì)于path部分Firefox、chrome、IE都是采用UTF-8編碼格式,對(duì)于Query String部分Firefox、chrome采用UTF-8,IE采用GBK。至于為什么會(huì)加上%,這是因?yàn)閁RL的編碼規(guī)范規(guī)定瀏覽器將ASCII字符非 ASCII 字符按照某種編碼格式編碼成 16 進(jìn)制數(shù)字然后將每個(gè) 16 進(jìn)制表示的字節(jié)前加上“%”。
當(dāng)然對(duì)于不同的瀏覽器,相同瀏覽器不同版本,不同的操作系統(tǒng)等環(huán)境都會(huì)導(dǎo)致編碼結(jié)果不同,上表某一種情況,對(duì)于URL編碼規(guī)則下任何結(jié)論都是過早的。由于各大瀏覽器、各個(gè)操作系統(tǒng)對(duì)URL的URI、QueryString編碼都可能存在不同,這樣對(duì)服務(wù)器的解碼勢(shì)必會(huì)造成很大的困擾,下面我們將已tomcat,看tomcat是如何對(duì)URL進(jìn)行解碼操作的。
解析請(qǐng)求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個(gè)方法把傳過來(lái)的 URL 的 byte[] 設(shè)置到 org.apache.coyote.Request 的相應(yīng)的屬性中。這里的 URL 仍然是 byte 格式,轉(zhuǎn)成 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);
}
從上面的代碼可知,對(duì)URI的解碼操作是首先獲取Connector的解碼集,該配置在server.xml中
<Connector URIEncoding="utf-8" />
如果沒有定義則會(huì)采用默認(rèn)編碼ISO-8859-1來(lái)解析。
對(duì)于Query String部分,我們知道無(wú)論我們是通過get方式還是POST方式提交,所有的參數(shù)都是保存在Parameters,然后我們通過request.getParameter,解碼工作就是在第一次調(diào)用getParameter方法時(shí)進(jìn)行的。在getParameter方法內(nèi)部它調(diào)用org.apache.catalina.connector.Request 的 parseParameters 方法,這個(gè)方法將會(huì)對(duì)傳遞的參數(shù)進(jìn)行解碼。下面代碼只是parseParameters方法的一部分:
//獲取編碼
String enc = getCharacterEncoding();
//獲取ContentType 中定義的 Charset
boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
if (enc != null) { //如果設(shè)置編碼不為空,則設(shè)置編碼為enc
parameters.setEncoding(enc);
if (useBodyEncodingForURI) { //如果設(shè)置了Chartset,則設(shè)置queryString的解碼為ChartSet
parameters.setQueryStringEncoding(enc);
}
} else { //設(shè)置默認(rèn)解碼方式
parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
}
}
從上面代碼可以看出對(duì)query String的解碼格式要么采用設(shè)置的ChartSet要么采用默認(rèn)的解碼格式ISO-8859-1。注意這個(gè)設(shè)置的ChartSet是在 http Header中定義的ContentType,同時(shí)如果我們需要改指定屬性生效,還需要進(jìn)行如下配置:
<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
上面部分詳細(xì)介紹了URL方式請(qǐng)求的編碼解碼過程。其實(shí)對(duì)于我們而言,我們更多的方式是通過表單的形式來(lái)提交。
我們知道通過URL方式提交數(shù)據(jù)是很容易產(chǎn)生亂碼問題的,所以我們更加傾向于通過表單形式。當(dāng)用戶點(diǎn)擊submit提交表單時(shí),瀏覽器會(huì)更加設(shè)定的編碼來(lái)編碼數(shù)據(jù)傳遞給服務(wù)器。通過GET方式提交的數(shù)據(jù)都是拼接在URL后面(可以當(dāng)做query String??)來(lái)提交的,所以tomcat服務(wù)器在進(jìn)行解碼過程中URIEncoding就起到作用了。tomcat服務(wù)器會(huì)根據(jù)設(shè)置的URIEncoding來(lái)進(jìn)行解碼,如果沒有設(shè)置則會(huì)使用默認(rèn)的ISO-8859-1來(lái)解碼。假如我們?cè)陧?yè)面將編碼設(shè)置為UTF-8,而URIEncoding設(shè)置的不是或者沒有設(shè)置,那么服務(wù)器進(jìn)行解碼時(shí)就會(huì)產(chǎn)生亂碼。這個(gè)時(shí)候我們一般可以通過new String(request.getParameter(“name”).getBytes(“iso-8859-1″),”utf-8″) 的形式來(lái)獲取正確數(shù)據(jù)。
對(duì)于POST方式,它采用的編碼也是由頁(yè)面來(lái)決定的即contentType。當(dāng)我通過點(diǎn)擊頁(yè)面的submit按鈕來(lái)提交表單時(shí),瀏覽器首先會(huì)根據(jù)ontentType的charset編碼格式來(lái)對(duì)POST表單的參數(shù)進(jìn)行編碼然后提交給服務(wù)器,在服務(wù)器端同樣也是用contentType中設(shè)置的字符集來(lái)進(jìn)行解碼(這里與get方式就不同了),這就是通過POST表單提交的參數(shù)一般而言都不會(huì)出現(xiàn)亂碼問題。當(dāng)然這個(gè)字符集編碼我們是可以自己設(shè)定的:request.setCharacterEncoding(charset) 。
更多建議: