從前面的介紹Url編碼文章中,我們了解了瀏覽器發(fā)起請(qǐng)求的字符處理過程。現(xiàn)在開始介紹tomcat服務(wù)器接收處理請(qǐng)求的過程。
httpClient模擬瀏覽器
在說tomcat服務(wù)器如何接收處理請(qǐng)求之前,我想先嘗試用httpClient工具來模擬瀏覽器發(fā)起請(qǐng)求的處理過程。其實(shí)這樣可以更加深刻理解瀏覽器。
演示:
1) 如果沒有url編碼就發(fā)送出去,那么服務(wù)器無法識(shí)別,于是報(bào)錯(cuò)。

2)? 如果先將請(qǐng)求中的參數(shù)值(value)進(jìn)行Urlencode后,這其實(shí)在模擬瀏覽器Get請(qǐng)求,服務(wù)器就正常識(shí)別了:

POST請(qǐng)求類似,不多演示。
tomcat處理請(qǐng)求值過程
Get和Post過來的鍵值對(duì),在我們調(diào)用request.getParameter("key")時(shí),tomcat會(huì)對(duì)value進(jìn)行轉(zhuǎn)碼。
我們知道鍵值對(duì)是經(jīng)過Url編碼的,這是client和server之間的行業(yè)約定。因此tomcat的處理也是會(huì)遵循此約定來處理。然而,Get和Post過來的數(shù)據(jù),tomcat雖然在value值編碼過程一樣,可是編碼的根據(jù)卻是不同點(diǎn)的。
Get請(qǐng)求:主要看tomcat的server.xml文件中的兩個(gè)參數(shù)配置:

URIEncoding :意思是,傳遞過來的value是UTF-8格式化得到的,因此tomcat會(huì)將utf-8字節(jié)轉(zhuǎn)化成unicode,進(jìn)而轉(zhuǎn)化為Utf-16內(nèi)碼。
useBodyEncodingForURI: 在默認(rèn)情況下,該參數(shù)為false。單詞中文意思大概是“按照request body字符的格式對(duì)待uri”。我們知道,常規(guī)情況下,get請(qǐng)求是沒有request body的??墒俏覀儾慌懦齈ost請(qǐng)求中URI也是帶有鍵值對(duì)的,當(dāng)這種少數(shù)情況出現(xiàn),tomcat就會(huì)根據(jù)request body 中的格式對(duì)待Uri鍵值對(duì)了。其實(shí)它的意思應(yīng)該是按照request請(qǐng)求頭中contentType指定的格式來對(duì)待uri鍵值對(duì)。一般情況下Request中我們都不會(huì)特地設(shè)置contentType,因?yàn)槲覀儽緛硪呀?jīng)知道客戶端發(fā)送過來的內(nèi)容會(huì)是什么格式的(因?yàn)榍耙粋€(gè)Response頭中我們已經(jīng)指定了)。如果沒有contentType指定,則tomcat會(huì)默認(rèn) value是 ISO8859-1格式的。
不過,這里有個(gè)細(xì)節(jié):URIEncoding 其實(shí)是受 useBodyEncodingForURI 牽制的。當(dāng)useBodyEncodingForURI = true ,則URIEncoding無效、tomcat只會(huì)根據(jù)Request頭中的ContentType對(duì)待鍵值對(duì)。當(dāng)=false,tomcat則只能根據(jù)URIEncoding來對(duì)待鍵值對(duì)。
例如:如果URIEncoding="gbk" useBodyEncodingForURI="true"都設(shè)置了,那么URIEncoding="gbk"不起作用。
如果我們沒有顯式指定URIEncoding 和 useBodyEncodingForURI,那么tomcat默認(rèn)鍵值對(duì)中的value是ISO8859-1格式的。
POST請(qǐng)求:tomcat只會(huì)根據(jù)request.setCharacterEncoding("CharsetName") 對(duì)待請(qǐng)求體(注意這里僅僅是針對(duì)請(qǐng)求體,而不是針對(duì)Post請(qǐng)求中的URI鍵值對(duì))。即,tomcat會(huì)認(rèn)為請(qǐng)求體字節(jié)是客戶端根據(jù)“CharsetName”格式化得來的,那么tomcat就會(huì)這樣轉(zhuǎn)碼:CharsetName --> Unicode -->Utf-16內(nèi)碼。
tomcat并不會(huì)根據(jù)request.setCharacterEncoding("CharsetName")處理Get請(qǐng)求,也不會(huì)根據(jù)URIEncode和useBodyEncodingForURI處理Post請(qǐng)求中的請(qǐng)求體。
tips1:上面說的結(jié)論,大家可以自己逐一測試,在此就不演示了。
綜上,tomcat對(duì)待Get、Post請(qǐng)求的處理是很分明的。既然那么分明,那么我們也得分明地設(shè)置好。一般地,我們會(huì)在Filter中request.setCharacterEncoding("CharsetName") 來獨(dú)立對(duì)待Post請(qǐng)求。
tips2:
其實(shí)上述的結(jié)論,我們可以直接從tomcat源碼中看出:org.apache.catalina.connector.Request#parseParameters()

注意:轉(zhuǎn)碼失敗容易丟失字節(jié)
假設(shè)客戶端通過sender=URLEncoder.encode("中文","GBK")對(duì)參數(shù)進(jìn)行編碼 再GET提交過來。如果server.xml中設(shè)置URIEncoding="utf-8",?沒有設(shè)置useBodyEncodingForURI="true"?,?那么,會(huì)使用utf-8對(duì)16進(jìn)制的參數(shù)進(jìn)行解碼,這時(shí)候?request.getParameter()獲得亂碼的中文參數(shù)值,并且通過new String(sender.getBytes("??utf-8??"), "gbk")也會(huì)亂碼,可能丟失字節(jié);原因我猜測如下:
按照GBK字符編碼,"中文" 是 四個(gè)字節(jié)的:"D6D0CEC4"。當(dāng)tomcat接收到數(shù)據(jù)后,以 utf-8來接收并解碼,會(huì)出現(xiàn)無法解析,
由上,我們?cè)谝?guī)劃轉(zhuǎn)碼配置時(shí)要謹(jǐn)慎。
響應(yīng)指定編碼
響應(yīng)體中指定編碼其實(shí)非常好理解。JVM中字符內(nèi)碼是UTF-16,當(dāng)需要輸出給client時(shí),我們會(huì)提前設(shè)置:response.setCharacterEncoding("CharsetName"),tomcat輸出前會(huì)將字符這樣轉(zhuǎn)碼:Utf-16內(nèi)碼--> Unicode -->"CharsetName",并且tomcat底層會(huì)幫我們?cè)O(shè)置響應(yīng)頭ContentType指定編碼為CharsetName。如此client就知道如何解析這字節(jié)流了。