论文部分内容阅读
摘要:本文详尽的分析了在中文环境下运用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,false,"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中使用,必须被转义成%这样的形式,事实上HttpClient就会自动将字符%转化成%。正是这种原因当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.
关键词: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,false,"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中使用,必须被转义成%这样的形式,事实上HttpClient就会自动将字符%转化成%。正是这种原因当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.