Java httpclient解决方案中的中文传递(2009-03-05 17:21:33)标签:杂谈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 协议规范的Http-Client 组件在与部分浏览器及服务器进行交互时会出现少量兼容性问题。
笔者在中文环境下用HttpClinet 开发校外资源访问系统的过程中碰到系列HttpClient 技术问题, 经过测试查证找到相应的解决办法, 这对解决HttpClient 编程问题, 特别是中文环境下Http-Client 编程具有较大的借鉴作用。
( 注: 本文编程的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 参数( 创建contentbody 的字符集) 的默认值为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 中使用, 必须被转义成%25 这样的形式, 事实上HttpClient 就会自动将字符%转化成%25。
正是这种原因当HttpClient 截获IE 浏览器的请求并将其转发到服务器时, 将会导致查询参数错误。
分析HttpClient 的源码发现, 这一转化是在URI 类中实现的, 经反复测试未能找到一种通过HttpClient 公开接口实现这一兼容性的方法。
目前只能采取修改URI 类源码重新编译的办法来实现:public static final BitSet allowed_query = new BitSet(256);// Static initializer for allowed_querystatic {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_HEA DER,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 编码, 修改一个此类中getChunk-SizeFromInputStream(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 参数, 而在一此特殊的场合又要求修改这一参数。