Apache HttpClient 使用(上)

HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。

目前最新的版本是4.5,支持HTTP 1.0/HTTP 1.1协议。正在开发中的5.0版本(还在Beta阶段)支持HTTP/2协议。

1. 特性

  1. 基于标准、纯净的java语言。实现了Http1.0和Http1.1
  2. 以可扩展的面向对象的结构实现了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
  3. 支持HTTPS协议。
  4. 通过Http代理建立透明的连接。
  5. 利用CONNECT方法通过Http代理建立隧道的https连接。
  6. Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos认证方案。
  7. 插件式的自定义认证方案。
  8. 便携可靠的套接字工厂使它更容易的使用第三方解决方案。
  9. 连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。
  10. 自动处理Set-Cookie中的Cookie。
  11. 插件式的自定义Cookie策略。
  12. Request的输出流可以避免流中内容直接缓冲到socket服务器。
  13. Response的输入流可以有效的从socket服务器直接读取相应内容。
  14. 在http1.0和http1.1中利用KeepAlive保持持久连接。
  15. 直接获取服务器发送的response code和 headers。
  16. 设置连接超时的能力。
  17. 实验性的支持http1.1 response caching。
  18. 源代码基于Apache License 可免费获取。

2. 使用方法

下面以一个简单的例子来说明一下HttpClient的使用方法。

2.1 pom

1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.4</version>
</dependency>

首先引入相关的Maven库。

2.2 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
String url = "http://www.baidu.com";
// 1. 使用默认的配置的httpclient
CloseableHttpClient client = HttpClients.createDefault();
// 2. 使用GET方法
HttpGet httpGet = new HttpGet(url);
InputStream inputStream = null;
CloseableHttpResponse response = null;
try {
//3.执行请求,获取响应
response = client.execute(httpGet);
//看请求是否成功,这儿打印的是http状态码
System.out.println(response.getStatusLine().getStatusCode());

//4.获取响应的实体内容,就是我们所要抓取得网页内容
HttpEntity entity = response.getEntity();

//5.将其打印到控制台上面
//方法一:使用EntityUtils
if (entity != null) {
System.out.println(EntityUtils.toString(entity, "utf-8"));
}
EntityUtils.consume(entity);

//方法二:使用inputStream(不能同时使用)
if (entity != null) {
inputStream = entity.getContent();

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);

}
}

} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

这段代码的用途是使用GET方法获取百度首页,并打印出来。

2.3 代码分析

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。

  1. 创建HttpClient对象。
    例子中使用的是org.apache.http.impl.client.CloseableHtttpClient,它是HttpClient接口的一个实例,创建该对象的最简单方法是:CloseableHttpClient client = HttpClients.createDefault();
    HttpClients是创建CloseableHttpClient的工厂,采用默认的配置来创建实例,一般情况下我们就用这个默认实例就可以了。以后我们会讨论如何自定义这个对象。

  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
    其他的类型还有:HttpHead, HttpPut, HttpDelete, HttpTrace, HttpOptions等。分别对应不同的Http请求方法:

方法 描述 是否包含主体
GET 从服务器获取一份文档
HEAD 只从服务器获取文档的首部
POST 向服务器发送需要处理的数据
PUT 将请求的主体部分存储在服务器上
TRACE 对可能经过代理服务器传送到服务器上去的报文进行追踪
OPTIONS 决定可以在服务器上执行哪些方法
DELETE 从服务器上删除一份文档
  1. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

  2. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个CloseableHttpResponse。

  3. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse
    getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。对getEntity获取的结果,有两种处理方法:
    方法一:使用EntityUtils工具类来处理。该类是官方提供的一个处理实体的工具类,toSting方法将返回的实体转换为字符串,但是官网不建议使用这个,除非响应实体从一个可信HTTP服务器发起和已知是有限长度的
    方法二:使用InputStream来读取。因为httpEntity.getContent方法返回的就是InputStream类型。这种方法是官网推荐的方式,需要记得要自己释放底层资源。

  4. 释放连接。无论执行方法是否成功,都必须释放连接
    如果使用EntityUtils来处理返回值,则可以使用EntityUtils.consume(entity)来释放资源

如果使用InputStream来处理返回值,则要关闭inputStream然后关闭response对象就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

2.4 关于资源释放

使用EntityUtils#consume这个方法会自动关闭底层的inputStream。如果不需要读取全部的实体,则可以直接关闭response,来种植对inputStream的读取。注意:关闭response会自动关闭对应的inputStream。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
}
} finally {
response.close(); // 直接关闭response
}

不推荐使用EntityUtils#toString方法,如果确实需要,最好先自己判断长度:entity.getContentLength()。这是因为toString方法默认最大的长度是Integer.MAX_VALUE(0x7fffffff)。这个长度太大了,很容易导致缓冲区溢出。

如果希望重复使用Entity,可以使用:new BufferedHttpEntity(entity)创建一个新对象缓存起来。

2.5 Entity

用于生成请求的Entity的类主要有四种:StringEntity, ByteArrayEntity, InputStreamEntity和FileEntity。
例子:FileEntity:

1
2
3
4
5
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file,
ContentType.create("text/plain", "UTF-8"));
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);

例子:

1
2
3
4
5
6
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);

3. 实例代码

3.1 普通GET请求

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建Httpclient对象  
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http GET请求
HttpGet httpGet = new HttpGet("http://www.baidu.com/s?wd=java");
CloseableHttpResponse response = null;
try {
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
...
}
}

3.2 带有参数的Get请求

1
2
3
4
// 定义请求的参数  
URI uri = new URIBuilder("http://www.baidu.com/s").setParameter("wd", "java").build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);

3.3 普通的POST请求

1
2
3
4
5
6
7
// 创建http POST请求  
HttpPost httpPost = new HttpPost("http://www.oschina.net/");
// 伪装浏览器请求
httpPost.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36");
// 执行请求
response = httpclient.execute(httpPost);

3.4 带有参数的Post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建http POST请求  
HttpPost httpPost = new HttpPost("http://www.oschina.net/search");

// 设置2个post参数,一个是scope、一个是q
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("scope", "project"));
parameters.add(new BasicNameValuePair("q", "java"));
// 构造一个form表单式的实体
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
// 将请求实体设置到httpPost对象中
httpPost.setEntity(formEntity);
// 伪装浏览器请求
httpPost.setHeader(
"User-Agent",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36");

// 执行请求
response = httpclient.execute(httpPost);

3.5 配置超时时间等

1
2
3
4
5
6
7
8
9
10
11
12
// 创建http GET请求  
HttpGet httpGet = new HttpGet("http://www.baidu.com/");

// 构建请求配置信息
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(1000) // 创建连接的最长时间
.setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间
.setSocketTimeout(10 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
// 设置请求配置信息
httpGet.setConfig(config);

几个函数的含义:

  • setConnectionRequestTimeout 从链接池获取一个连接的最长等待时间;
  • setConnectTimeout 创建链接的最长等待时间;
  • setSocketTimeout 获取数据的最长等待时间;
  • setStaleConnectionCheckEnabled 检查链接状态是否可用。这个选项会花费最长30ms时间已确定一个连接是否可用。对性能要求高的应用不要开启这个选项。

热评文章