httpclient 是 apache jakarta common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 http 协议的客户端编程工具包,并且它支持 http 协议最新的版本和建议。
以下列出的是 httpclient 提供的主要的功能,要知道更多详细的功能可以参见 httpclient 的凯发k8网页登录主页。
- 实现了所有 http 的方法(get,post,put,head 等)
- 支持自动转向
- 支持 https 协议
- 支持代理服务器等
下面将逐一介绍怎样使用这些功能。首先,我们必须安装好 httpclient。
- httpclient 可以在下载
- httpclient 用到了 apache jakarta common 下的子项目 logging,你可以从这个地址下载到 common logging,从下载后的压缩包中取出 commons-logging.jar 加到 classpath 中
- httpclient 用到了 apache jakarta common 下的子项目 codec,你可以从这个地址.cgi 下载到最新的 common codec,从下载后的压缩包中取出 commons-codec-1.x.jar 加到 classpath 中
下面是个我写的程序:
package com.newpalm.unicomfetch.threads;
import java.util.vector;
import org.apache.commons.httpclient.cookie;
import org.apache.commons.httpclient.httpclient;
import org.apache.commons.httpclient.namevaluepair;
import org.apache.commons.httpclient.cookie.cookiepolicy;
import org.apache.commons.httpclient.cookie.cookiespec;
import org.apache.commons.httpclient.methods.getmethod;
import org.apache.commons.httpclient.methods.postmethod;
import org.htmlparser.node;
import org.htmlparser.parser;
import org.htmlparser.tags.tablecolumn;
import org.htmlparser.tags.tablerow;
import org.htmlparser.tags.tabletag;
import org.htmlparser.visitors.objectfindingvisitor;
/**
* 用来演示登录表单的示例
* @author liudong
*/
public class formlogindemo {
public static void main(string[] args) throws exception{
parser parser = null;
objectfindingvisitor visitor = null;
httpclient client = new httpclient();
//模拟登录页面
postmethod post = new postmethod("");
namevaluepair name = new namevaluepair("txthandsetnumber", "123456");
namevaluepair pass = new namevaluepair("txtpassword", "123456");
namevaluepair __viewstate = new namevaluepair("__viewstate", "ddwtmtuxmzkxntq0mzt0pdtspgk8mt47pjtsphq8o2w8atwxpjs o2w8ddxwpgw8vgv4dds o2w8xdxtyxjxdwvlierpcmvjdglvbj0idxaiigjlagf2aw9ypsjzy3jvbgwiicbozwlnahq9jze1mccgc2nyb2xszgvsyxk9mzawig9ubw91c2vvdmvypsd0aglzlnn0b3aokscgb25tb3vzzw91dd0ndghpcy5zdgfydcgpj1w xdxpbwcgc3jjpsjpbwfnzs9kzwzhdwx0x2nhbgxfawnvbi5nawyiihdpzhropsi0iibozwlnahq9ijcixd4mbmjzcfw7jm5ic3bco1w8ysbocmvmid0iiyigb25jbgljaz0ib3blbldpbmrvdygnl0j1bgxldglulmfzchg/qnvsbgv0aw5jzd0ymycpilw c3dkujrliqhmjqxlhaxms6jmhi/kuovpoblcpc9hxd4kxdxiclw xdxiclw xdwvbwfycxvlzvw oz4 ozs oz4 oz4 o2w8ynrutg9naw47pj4pkegdivj1jmrdgjas7sgzd39jzg==");
namevaluepair btnloginx = new namevaluepair("btnlogin.x", "0");
namevaluepair btnloginy = new namevaluepair("btnlogin.y", "5");
post.setrequestbody(new namevaluepair[]{name,pass,__viewstate,btnloginx,btnloginy});
int status = client.executemethod(post);
post.releaseconnection();
//查看cookie信息
cookiespec cookiespec = cookiepolicy.getdefaultspec();
cookie[] cookies = client.getstate().getcookies();
if (cookies.length == 0) {
system.out.println("none");
} else {
for (int i = 0; i < cookies.length; i) {
system.out.println(cookies[i].tostring());
}
}
//访问所需的页面
getmethod get = new getmethod("");
client.executemethod(get);
visitor = new objectfindingvisitor(tabletag.class);
parser = new parser();
parser.setinputhtml(get.getresponsebodyasstring());
parser.setencoding("gbk");
parser.visitallnodeswith(visitor);
//取得要解析的页面数
node[] tables = visitor.gettags();
tabletag tabletag = (tabletag) tables[tables.length-1];
tablerow[] rows = tabletag.getrows();
tablerow row = rows[0];
tablecolumn[] col = row.getcolumns();
int pagenumber = integer.parseint(col[0].getchildrenhtml().substring(25, 29));
get.releaseconnection();
for(int i=1;i<pagenumber;i){
postmethod pt = new postmethod("");
namevaluepair txtpage = new namevaluepair("txtpage",integer.tostring(i));
__viewstate = new namevaluepair("__viewstate", "")
namevaluepair __eventtarget = new namevaluepair("__eventtarget", "");
namevaluepair __eventargument = new namevaluepair("__eventargument", "");
namevaluepair tbmdn = new namevaluepair("tbmdn", "");
namevaluepair tbservicetype = new namevaluepair("tbservicetype", "");
namevaluepair tbstarttime = new namevaluepair("tbstarttime", "");
namevaluepair tbendtime = new namevaluepair("tbendtime", "");
namevaluepair btngotox = new namevaluepair("btngoto.x", "26");
namevaluepair btngotoy = new namevaluepair("btngoto.y", "13");
pt.setrequestbody(new namevaluepair[]{__eventtarget,__eventargument,__viewstate,tbmdn,tbservicetype,tbstarttime,tbendtime,txtpage,btngotox,btngotoy});
int a = client.executemethod(pt);
parser.setinputhtml(pt.getresponsebodyasstring());
parser.setencoding("gbk");
parser.visitallnodeswith(visitor);
tables = visitor.gettags();
tabletag = (tabletag) tables[tables.length-3];
rows = tabletag.getrows();
row = rows[1];
col = row.getcolumns();
system.out.println(col[4].getchildrenhtml().tostring());
get.releaseconnection();
}
}
}
下面介绍在使用httpclient过程中常见的一些问题。
某目标页的编码可能出现在两个地方,第一个地方是服务器返回的http头中,另外一个地方是得到的html/xml页面中。
- 在http头的content-type字段可能会包含字符编码信息。例如可能返回的头会包含这样子的信息:content-type:
text/html;
charset=utf-8。这个头信息表明该页的编码是utf-8,但是服务器返回的头信息未必与内容能匹配上。比如对于一些双字节语言国家,可能服务
器返回的编码类型是utf-8,但真正的内容却不是utf-8编码的,因此需要在另外的地方去得到页面的编码信息;但是如果服务器返回的编码不是utf-
8,而是具体的一些编码,比如gb2312等,那服务器返回的可能是正确的编码信息。通过method对象的getresponsecharset()方
法就可以得到http头中的编码信息。
- 对于象xml或者html这样的文件,允许作者在页面中直接指定编码类型。比如在html中会有这样的标签;或者在xml中会有这样的标签,在这些情况下,可能与http头中返回的编码信息冲突,需要用户自己判断到底那种编码类型应该
是真正的编码。
根据rfc2616中对自动转向的定义,主要有两种:301和302。301表示永久的移走(moved
permanently),当返回的是301,则表示请求的资源已经被移到一个固定的新地方,任何向该地址发起请求都会被转到新的地址上。302表示暂时
的转向,比如在服务器端的servlet程序调用了sendredirect方法,则在客户端就会得到一个302的代码,这时服务器返回的头信息中
location的值就是sendredirect转向的目标地址。
httpclient支持自动转向处理,但是象post和put方式这种要求接受后继服务的请求方式,暂时不支持自动转向,因此如果碰到post方
式提交后返回的是301或者302的话需要自己处理。就像刚才在postmethod中举的例子:如果想进入登录bbs后的页面,必须重新发起登录的请
求,请求的地址可以在头字段location中得到。不过需要注意的是,有时候location返回的可能是相对路径,因此需要对location返回的
值做一些处理才可以发起向新地址的请求。
另外除了在头中包含的信息可能使页面发生重定向外,在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是:。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是,在上面那
个标签中url的值也可以是一个相对地址,如果是这样的话,需要对它做一些处理后才可以转发。
httpclient提供了对ssl的支持,在使用ssl之前必须安装jsse。在sun提供的1.4以后的版本中,jsse已经集成到jdk中,
如果你使用的是jdk1.4以前的版本则必须安装jsse。jsse不同的厂家有不同的实现。下面介绍怎么使用httpclient来打开https连
接。这里有两种方法可以打开https连接,第一种就是得到服务器颁发的证书,然后导入到本地的keystore中;另外一种办法就是通过扩展
httpclient的类来实现自动接受证书。
方法1,取得证书,并导入本地的keystore:
- 安装jsse
(如果你使用的jdk版本是1.4或者1.4以上就可以跳过这一步)。本文以ibm的jsse为例子说明。先到ibm网站上下载jsse的安装包。然后解
压开之后将ibmjsse.jar包拷贝到"lib"ext"目录下。
- 取得并且导入证书。证书可以通过ie来获得:1. 用ie打开需要连接的https网址,会弹出如下对话框:
2. 单击”view certificate”,在弹出的对话框中选择”details”,然后再单击”copy to file”,根据提供的向导生成待访问网页的证书文件
3. 向导第一步,欢迎界面,直接单击”next”,
4. 向导第二步,选择导出的文件格式,默认,单击”next”,
5. 向导第三步,输入导出的文件名,输入后,单击”next”,
6. 向导第四步,单击”finish”,完成向导
7. 最后弹出一个对话框,显示导出成功
- 用keytool工具把刚才导出的证书倒入本地keystore。keytool命令在"bin"下,打开命令行窗口,并到"lib"security"目录下,运行下面的命令:
keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourentry1 -file your.cer
|
其中参数alias后跟的值是当前证书在keystore中的唯一标识符,但是大小写不区分;参数file后跟的是刚才通过ie导出的证书所在的路径和文件名;如果你想删除刚才导入到keystore的证书,可以用命令:
keytool -delete -keystore cacerts -storepass changeit -alias yourentry1
|
- 写程序访问https地址。如果想测试是否能连上https,只需要稍改一下getsample例子,把请求的目标变成一个https地址。
getmethod getmethod = new getmethod("https://www.yourdomain.com");
|
运行该程序可能出现的问题:
1. 抛出异常java.net.socketexception: algorithm ssl not available。出现这个异常可能是因为没有加jsseprovider,如果用的是ibm的jsse provider,在程序中加入这样的一行:
if(security.getprovider("com.ibm.jsse.ibmjsseprovider") == null)
security.addprovider(new ibmjsseprovider());
|
或者也可以打开"lib"security"java.security,在行
security.provider.1=sun.security.provider.sun
security.provider.2=com.ibm.crypto.provider.ibmjce
|
后面加入security.provider.3=com.ibm.jsse.ibmjsseprovider
2. 抛出异常java.net.socketexception: ssl implementation not available。出现这个异常可能是你没有把ibmjsse.jar拷贝到"lib"ext"目录下。
3. 抛出异常javax.net.ssl.sslhandshakeexception: unknown
certificate。出现这个异常表明你的jsse应该已经安装正确,但是可能因为你没有把证书导入到当前运行jre的keystore中,请按照前
面介绍的步骤来导入你的证书。
方法2,扩展httpclient类实现自动接受证书
因为这种方法自动接收所有证书,因此存在一定的安全问题,所以在使用这种方法前请仔细考虑您的系统的安全需求。具体的步骤如下:
- 提供一个自定义的socket
factory(test.mysecureprotocolsocketfactory)。这个自定义的类必须实现接口
org.apache.commons.httpclient.protocol.secureprotocolsocketfactory,在实现接口
的类中调用自定义的x509trustmanager(test.myx509trustmanager),这两个类可以在随本文带的附件中得到
- 创建一个org.apache.commons.httpclient.protocol.protocol的实例,指定协议名称和默认的端口号
protocol myhttps = new protocol("https", new mysecureprotocolsocketfactory (), 443);
|
- 注册刚才创建的https协议对象
protocol.registerprotocol("https ", myhttps);
|
- 然后按照普通编程方式打开https的目标地址,代码请参见test.nocertificationhttpsgetsample
httpclient中使用代理服务器非常简单,调用httpclient中setproxy方法就可以,方法的第一个参数是代理服务器地址,第二个参数是端口号。另外httpclient也支持socks代理。