摘要:本文詳盡的分析了在中文環境下運用Apach Commons HttpClient進行編程時出現的幾個常見問題。針對每個問題,本文均給出較為完善的解決方案,對中文環境下的Apach Commons HttpClient編程具有極大的現實參考價值。
關鍵詞:Apach Commons HttpClient、程序設計、中文操作系統
中圖分類號:TP393文獻標識碼:A文章編號:1009-3044(2008)22-782-02
Analyses of Apach Commons HttpClient Programming in Chinese OS Platform
HONG Liang,TIAN Zhi-bin
(Hunan Normal University, Changsha 410081, China)
Abstract: The paper describes some common problems with Apach Commons HttpClient programming in Chinese OS platform. To every problem,the paper gives preferable solution which may be helpful to Apach Commons HttpClient programming.
Key words: apach commons HttpClient; programming; Chinese OS platform
1 Commons HttpClient開源項目簡介
Http協議是一種應用十分廣泛的網絡應用層協議。在Java網絡編程中我們會經常碰到Http協議編程,雖然JDK提供了HttpURLConnection編程接口對Http協議進行支持,但是由于協議應用本身的復雜性,使得在大量實際項目單純使用JDK進行Http編程仍然相對比較困難。針對這種情況,開源軟件組織Apach推出了HttpClient開源組件,并且提供穩定持續的升級版本,因此在實際項目中采用HttpClient組件進行Http協議編程是一種高效經濟的解決方案。
2 Commons HttpClient中文環境下編程常見問題
由于HttpClient組件設計的高度靈活性及易用性,應用HttpClient組件進行編程本身并不復雜。但是由于Java編程環境自身容易出現字符編碼問題,衍生于Java語言并主要由英語語系國家技術人員推出的HttpClient組件自然在中文環境中會存在一定的編碼問題,同時由于部分Web瀏覽器及Web服務器并未嚴格實現標準Http協議規范,使得比較嚴格遵循標準Http協議規范的HttpClient組件在與部分瀏覽器及服務器進行交互時會出現少量兼容性問題。筆者在中文環境下用HttpClinet開發校外資源訪問系統的過程中碰到系列HttpClient技術問題,經過測試查證找到相應的解決辦法,這對解決HttpClient編程問題,特別是中文環境下HttpClient編程具有較大的借鑒作用。(注:本文編程的HttpClient組件版本為:Release 3.1 Beta 1)
3 Commons HttpClient編程的典型問題及解決辦法
3.1 URL中文參數無法識別的問題
通常情況在Commons HttpClient編程中我們用下列語句就可以向一個目標服務器提交一個Web請求:
HttpClient client=new HttpClient();
GetMethod method = new GetMethod(url);//本示例使用Get方法,當然也可使用Post方法PostMethod method//=new PostMethod(url);
client.executeMethod(method);
InputStream receiver=method.getResponseBodyAsStream();
如果URL沒有中文參數,以上語句執行起來沒有任何問題,但是如果URL中含有中文字符,中文參數將無法被Web服務器識別,程序雖然可以正常運行,但卻無法得到正確結果。同時返回的Http響應頭,如果含有中文字符也將出現亂碼。分析源碼發現,這是HttpClient中的HttpElementCharset參數(創建HTTP headers的字符集)的默認值為US-ASCII,ContentCharset參數(創建content body的字符集)的默認值為ISO-8859-1的原故,因此需要使用下列語句改變這些參數的默認值:
client = new HttpClient();
params=client.getParams();
params.setHttpElementCharset(\"GBK\");
params.setContentCharset(\"GBK\");
或者使用:
method.getParams().setHttpElementCharset(\"GBK\");
method.getParams().setCredentialCharset(\"GBK\");
method.getParams().setContentCharset(\"GBK\");
第一種方式可能會更好,它在HttpClient初始化時就對字符默認參數進行了設置,作用范圍更廣。第二種方式僅對使用具體的請求方法時起作用,字符集的作用范圍有限。一般情況下我們用上述語句即可實現中文字符的正確識別。但要深入使用我們會發現如果URL中文參數含有中文特殊字符(如“{”,“}”等符號),程序將拋出URIException異常,導致請求無法完成。繼續研究HttpClient的源代碼發現,URI類是負責解析URL的核心類,控制URL編碼字符集是ProtocolCharset參數,默認情況下為UTF-8編碼,同時還在URI的構造函數提供了一個邏輯值來決定是否過濾非法字符,而恰恰一些中文特殊字符也被當成非法的字符過濾掉了,根據產生問題的原因,我們在基本解決中文編碼問題的基礎上結合以下容錯的方法來最終保證中文編碼的正確識別:
String url=….;
try
{strUri=new URI(uri,true,\"GBK\");}
catch(URIException e)
{strUri=new URI(uri,1,\"GBK\");}
method.setURI(strUri);
經測試上述辦法解決大量的已知URL中文參數不能識別的問題,當然如果在程序個別地方上述方法依然不能奏效,你還可以使用Java中文編碼轉換的基礎解決方法,借助ISO-8859-1標準編碼過渡,能夠輕易將JVM在網絡傳送過程轉換成的UTF8編碼還原成GBK編碼,下面的代碼實現了這一功能:
byte [] b;
String utf8_value;
utf8_value = request.getParameter(\"NAME\");//從HTTP流中取\"NAME\"的UTF8數據
b = utf8_value.getBytes(\"8859_1\"); //中間用ISO-8859-1過渡
String name = new String(b, \"GB2312\"); //轉換成GB2312字符
3.2 URL中的%問題
部分不太規范的中文網站中使用%作URL中參數的一部分傳遞給服務器,而中文Windows中的許多主流瀏覽器也兼容這一例外。事實上%是作為Http協議中的UrlEncode編碼的保留字符不能直接在URL中使用,必須被轉義成%25這樣的形式,事實上HttpClient就會自動將字符%轉化成%25。正是這種原因當HttpClient截獲IE瀏覽器的請求并將其轉發到服務器時,將會導致查詢參數錯誤。分析HttpClient的源碼發現,這一轉化是在URI類中實現的,經反復測試未能找到一種通過HttpClient公開接口實現這一兼容性的方法。目前只能采取修改URI類源碼重新編譯的辦法來實現:
public static final BitSet allowed_query = new BitSet(256);
// Static initializer for allowed_query
static {
allowed_query.or(uric);
allowed_query.clear('%');將源碼中此語句去掉或注釋即可}
3.3 Cookie整合問題
IE和Firefox在發送請求給服務器時會把所有的cookie打包成一個,然后在這個cookie里按照分號把每一項隔開,中間有個空格。但httpclient會在header里構建多個cookie項,每一項只含有一個cookie,這同IE是不一樣的。為了使用IE這種發送方式,我們需要設置一個請求方法中的一個Cookie參數,參考設置如下:
method.getParams().setParameter(HttpMethodParams.SINGLE_COOKIE_HEADER,new Boolean(true)); //使多個cookie合并成一個cookie頭
3.4 chunked編碼不規范的問題
有時候Web服務器生成HTTP Response是無法在Header就確定消息大小的,這時一般來說服務器將不會提供Content-Length的頭信息,而采用Chunked編碼動態的提供body內容的長度。 Chunked編碼使用若干個Chunk串連而成,由一個標明長度為0的chunk標示結束。使用十分廣泛的Tomcat Web服務器大量采用了Chunked編碼方案,然而早期的Tomcat Web服務器實現并不十分規范,并沒有以標明長度為0的chunk標示內容傳輸結束。因此HttpClient在接收這些早期的Tomcat Web服務器的Http響就會導致解析錯誤。分析HttpClient源碼發現,ChunkedInputStream類負責在HttpClient中解析chunked編碼,修改一個此類中getChunkSizeFromInputStream(final InputStream in)方法,可使標準的和上述非標準的chunked編碼均可正常解析。具體修改方法如下:
private static int getChunkSizeFromInputStream(final InputStream in)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// States: 0=normal, 1=\\r was scanned, 2=inside quoted string, -1=end
int state = 0;
while (state != -1) {
int b = in.read();
if (b == -1) {
return 0;//新增加語句
throw new IOException(\"chunked stream ended unexpectedly\");//原始語句,需將其去掉或注釋掉}
3.5 Host頭無法修改的問題
HttpClient本身在HttpMethodBase類中提供增加和修改Http請求頭的addRequestHeader和setRequestHeader方法,然而卻無法修改Host參數,而在一此特殊的場合又要求修改這一參數。經分析源碼發現,HttpClient在設置Http請求頭設計成了不能修改的固定模式。因此為適應特殊要求,只能修改和重編譯HttpMethodBase類。具體修改方法如下:
protected void addRequestHeaders(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace(\"enter HttpMethodBase.addRequestHeaders(HttpState, \"
+ \"HttpConnection)\");
addUserAgentRequestHeader(state, conn);
addHostRequestHeader(state, conn); //原始語句,需將其去掉或注釋掉
addCookieRequestHeader(state, conn);
addProxyConnectionHeader(state, conn);}
參考文獻:
[1] Hypertext Transfer Protocol-HTTP/1.1[S/OL].RFC2068.1997-01.http://jakarta.apache.orglcommons/http-client/userguide.html.
[2] Apache Jakarta Common HttpClient[EB/OL].(2004-03).http://jakarta.apache.org/commons/httpclient/userguide.html.
[3] Apach Commons HttpClient [EB/OL].(2007-02).http://jakarta.apache.org/commons/httpclient
[4] HttpClient入門[EB/OL].(2006-12).http://www.ibm.com/developerworks/cn/opensource/os-httpclient/
[5] Harold E R,著.Java網絡編程[M].朱濤江,林劍,譯.北京:中國電力出版社,2005.