[转]httpclient编码
這幾天都在糾結Java Web開發(fā)中的中文編碼問題。其實,很多Java Web開發(fā)者都被中文編碼“折磨”過,網絡上有大量的討論。以前我也讀過這方面的博文,讀完后感覺似乎懂了,好像知道了編碼問題的原因和解決方法。但是, 一旦投入到實際開發(fā)中,發(fā)現自己其實沒懂,囧!
??? 連續(xù)糾結了幾天,總算對前因后果有個清晰地認識,故“略談”一下。之所以略談,是因為我并非(也沒有能力)完整地闡述Java Web開發(fā)的中文編碼問題,而是就事論事地總結這幾天遇到的問題和收獲。
問題?
??? 使用HttpClient 3.x發(fā)送GET或POST請求,請求參數中包含中文。服務器是Tocmat 5.5,通過斷點調試,發(fā)現Servlet拿到的中文參數是亂碼。顯然,HttpClient和Tomcat沒有就中文參數的編碼達成一致。
??? 于是,開始深入HttpClient和Tomcat的代碼,結合斷點調試,發(fā)現中文編碼問題并不是想象中的那么簡單。
術語約定?
??? 為了使得描述更加請求,我對本文中出現的“術語”進行約定,避免一詞多義引起的歧義。
??? 另外,編碼了的數據必然需要解碼,因此encoding和decoding往往是同現的。不過為了敘述簡練,下文需要兩者同現的地方,僅使用encoding。
哪些數據需要encoding??
??? 在研究中文編碼問題前,我們首先要弄清一個問題:哪些數據需要encoding?
??? 一個Http請求的數據大致包括URI、Header、和Body三個部分。這三個部分貌似都需要encoding,不過我這次只涉及到URI和Body,因此 就不討論Header了。
??? 我們一般關心請求參數的中文編碼問題。雖然URI Path中也可以包括中文,但是。。。這不是給自己找麻煩嗎?
??? GET的請求參數在QueryString中,是URI的一部分。因此,對于GET請求,我們需要關注,URI是如何encoding的?
??? POST的請求參數在Body中,因此,對于POST請求,我們則需要關注,Body是如何encoding的?
??? 對于HttpClient和Tomcat來說,encoding和decoding本身是很容易的事情,關鍵是要知道charset是什么?要不通過API進行設置,要不通過配置文件進行配置。麻煩的是,URI和Body的charset還可以不一樣,使用不同的方法進行設置和配置。
??? HttpClient是一個類庫,通過自身提供的API對URI和Body的charset進行設置;Tomcat通過配置項和Servlet API,對URI和Body的charset進行設置。
HttpClient如何設置charset??
??? 我們先看看如何設置GET請求QueryString的charset,然后看看POST請求Body的charset,最后看看如何獲取響應數據的charset。
????設置GET請求QueryString的charset
??? 我們通過GETMethod的setQueryString方法設置QueryString。setQueryString方法有兩種原型,我們分別看看。???
[java]?view plaincopy
??? 原型一以參數鍵值對的形式設置QueryString,使用固定的UTF-8作為charset,而且做URLEncode。因此,調用原型一之后,HttpClient就不會對QueryString再做任何encoding了。
??? 如果不想使用UTF-8,那么可以使用原型二。
???
[java]?view plaincopy ?
??? 原型二直接設置QueryString的內容。需要注意的是,queryString參數一定是按照某種charset進行URLEncode之后的字符串?。
??? 另外,也可以通過GETMethod的構造函數,直接設置URLEncode之后的uri?(包括了QueryString):
???
[java]?view plaincopy
??? 設置POST請求Body的charset?
??? 首先,我們可以在POST請求中的Header中設置Content-Type:
???
[java]?view plaincopy
??? 在這里,Body的charset就UTF-8。
??? 其次,如果沒有設置Content-Type,我們還可以設置HttpClientParam的ContentCharset:
????
??
??? 然后,如果沒有設置HttpMethodParams的ContentCharset,我們還可以設置HttpMethodParams的ContentCharset:
???
??? 這三種設置方法的優(yōu)先級依次遞增,也就是說如果同時設置,則以后面的為準。如果都沒有設置,默認charset是ISO-8859-1。
????響應數據的charset?
??? 我們一般使用HttpMethodBase(GETMethod和PostMethod的父類)的getResponseBody系列方法獲取響應數據。getResponseBody系列方法包括:
???
?
??? 我比較喜歡getResponseBodyAsString方法,因為返回值類型是String,直接可以使用。不過,提到String就必須想到charset?。響應數據的charset肯定由Web Server(Tomcat)設置的,HttpMethodBase是怎么知道的呢?
??? 我們看看getResponseBodyAsString()方法的代碼:
???
?
??? 顧名思義,getResponseCharSet方法的功能就是獲取響應數據的charset。那就看看她的代碼吧:
???
??? 可見,getResponseCharSet方法Content-Type Header獲取響應數據的charset。這要求Servlet必須正確設置response的Content-Type Header?。
Tomcat如何設置charset??
??? 即使HttpClient正確設置了charset,Tomcat還要知道charset是什么,才能正確decoding。我們先看看如何設置GET請求QueryString的charset,然后看看POST請求Body的charset,最后看看Servlet響應數據的charset。
????設置GET請求QueryString的charset?
??? Tomcat通過URI的charset來設置QueryString的charset。我們可以在Tomcat根目錄下conf/server.xml?中進行配置。
???
??? URIEncoding屬性就是URI的charset,上述配置表示 Tomcat認為URI的charset就是UTF-8。如果HttpClient也使用UTF-8作為QueryString的charset,那么 Tomcat就可以正確decoding。詳情可以參考org.apache.tomcat.util.http.Parameters類的handleQueryParameters的方法:
???
[java]?view plaincopy
??? Tomcat在啟動的過程中,如果從conf/server.xml中讀取到URIEncoding屬性,就會設置queryStringEncoding的值。當Tomcat處理HTTP請求時,上述方法就會被調用。
????默認的server.xml是沒有配置URIEncoding屬性的,需要我們手動設置?。如果沒有設置,Tomcat就會采用一種稱為“fast conversion”的方式解析QueryString。詳情可以參考org.apache.tomcat.util.http.Parameter類的urlDecode方法。
??? useBodyEncodingForURI是與URI charset相關的另一個屬性。如果該屬性的值為true,則Tomcat將使用Body的charset作為URI的charset。下一節(jié)將介紹Tomcat如何設置Body的charset。如果Tomcat沒有設置Body的charset,那么將使用HTTP請求Content-Type Header中的charset。如果HTTP請求中沒有設置Content-Type Header,則使用ISO-8859-1作為默認charset。詳情參見org.apache.catalina.connector.Request的parseParmeters方法:
???
[java]?view plaincopy
?????默認的server.xml是沒有配置useBodyEncodingForURI屬性的,需要我們手動設置?。如果沒有設置,Tomcat則認為其值為false。需要注意的是,如果URIEncoding和useBodyEncodingForURI同時設置,而且Body的charset已經設置,那么將以Body的charset為準?。
????設置POST請求Body的charset?
??? 設置Body charset的方法很簡單,只要調用javax.servlet.ServletRequest接口的setCharacterEncoding方法即可,比如request.setCharacterEncoding("UTF-8")。需要注意的是,該方法必須在讀取任何請求參數之前調用,才有效果。詳情可以參見該方法的注釋:
????
???? 也就是說,我們只有在調用getParameter或getReader方法之前,調用setsetCharacterEncoding方法,設置的charset才能奏效。
????響應數據的charset
??? 設置響應數據charset的方法很簡單,只要調用javax.servlet.ServletResponse接口的setContentType或setCharacterEncoding方法即可,比如response.setContentType("text/html;charset=UTF-8")或response.setCharacterEncoding("UTF-8")。需要注意的是,這兩個方法的調用時機也是有講究的,詳情可以參見他們的注釋。
?
??? 如果Servlet正確設置了響應數據的charset,那么HTTP響應數據中就會包含Content-Type Header。HttpClient的getResponseBodyAsString方法就可以正確decoding響應數據。
總結?
??? 在開發(fā)Java Web應用的過程中,遇到中文亂碼問題,應該是一件正常的事情。我們不必首先懷疑HtpClient或Tomcat有莫名奇妙的bug,往往都是我們使用不當或配置不當。凡事總有原因,總要相信科學嘛!如果想徹底了解中文編碼的前因后果,我覺得可以從HTTP規(guī)范、Servlet規(guī)范、HttpClient的API文檔和Tomcat的配置文檔入手,必要時可以追蹤HttpClient和Tomcat的代碼。
總結
以上是生活随笔為你收集整理的[转]httpclient编码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 定时器基础
- 下一篇: 阿里云Redis (安装包安装篇)