HttpClient简介
HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
使用方法
使用HttpClient发送请求、接收响应步骤:
1. 创建HttpClient对象。
2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
6. 释放连接。无论执行方法是否成功,都必须释放连接
基本概念
请求执行
Http请求
HttpClient 支持 HTTP/1.1 这个版本定义的所有 Http 方法:GET,HEAD,POST,PUT,DELETE,TRACE 和OPTIONS。对于每一种 http 方法,HttpClient 都定义了一个相应的类:
HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace 和 HttpOpquertions。
HttpClient 提供 URIBuilder 工具类来简化 URIs 的创建和修改过程。
Http响应
服务器收到客户端的 http 请求后,就会对其进行解析,然后把响应发给客户端,这个响应就是HTTP response.HTTP 响应第一行是协议版本,之后是数字状态码和相关联的文本段。
如:response.getStatusLine().getStatusCode() == 200 表示响应成功。
消息头
一个 Http 消息可以包含一系列的消息头,用来对 http 消息进行描述,比如消息长度,消息类型等等。HttpClient 提供了方法来获取、添加、移除、枚举消息头。
如:
//设置头信息,模拟浏览器
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
Http实体
HttpClient 根据来源的不同,划分了三种不同的 Http 实体内容。
- streamed 流式: 内容是通过流来接受或者在运行中产生。特别是,streamed 这一类包含从 http 响应中获取的实体内容。一般说来,streamed 实体是不可重复的。
- self-contained 自我包含式:内容在内存中或通过独立的连接或其它实体中获得。selfcontained 类型的实体内容通常是可重复的。这种类型的实体通常用于关闭 http 请求。
- wrapping 包装式: 这种类型的内容是从另外的 http 实体中获取的。
确保底层的资源连接被释放
为了确保系统资源被正确地释放,我们要么管理 Http 实体的内容流、要么关闭 Http 响应。
关闭 Http 实体内容流和关闭 Http 响应的区别在于,前者通过消耗掉 Http 实体内容来保持相关的http 连接,然后后者会立即关闭、丢弃 http 连接。
HttpEntity 的 writeTo(OutputStream)方法,当 Http 实体被写入到 OutputStream 后,也要确保释放系统资源。如果这个方法内调用了 HttpEntity 的 getContent()方法,那么它会有一个
java.io.InpputStream 的实例,我们需要在 finally 中关闭这个流。
消耗 HTTP 实体内容
HttpClient 推荐使用 HttpEntity 的 getConent()方法或者 HttpEntity 的 writeTo(OutputStream)方法来消耗掉 Http 实体内容。HttpClient 也提供了 EntityUtils 这个类,这个类提供一些静态方法可以更容易地读取 Http 实体的内容和信息。和以 java.io.InputStream 流读取内容的方式相比,EntityUtils 提供的方法可以以字符串或者字节数组的形式读取 Http 实体。但是,强烈不推荐使用 EntityUtils 这个类,除非目标服务器发出的响应是可信任的,并且 http 响应实体的长度不会过大。
要把 Http 实体内容缓存在内存或者磁盘上。最简单的方法就是把 Http Entity 转化成 BufferedHttpEntity。
创建 HTTP 实体内容
HttpClient 提供了一些类,这些类可以通过 http 连接高效地输出 Http 实体内容。HttpClient 提供的这几个类涵盖的常见的数据类型,如 String,byte 数组,输入流,和文件类型:
StringEntity,ByteArrayEntity,InputStreamEntity,FileEntity。
推荐,通过继承 HttpEntity 这个自包含的类来自定义 HttpEntity 类。
HttpClient 提供了 UrlEncodedFormEntity 这个类来帮助模拟提交HTML表单。
* ResponseHandler *
最简单也是最方便的处理 http 响应的方法就是使用 ResponseHandler 接口,这个接口中有handleResponse(HttpResponse response)方法。
当使用 ResponseHandler 时,HttpClient 会自动地将 Http 连接释放给 Http 管理器,即使 http 请求失败了或者抛出了异常。
HttpClient 接口
HttpClient 的线程安全性
HttpClient 已经实现了线程安全。
HttpClient 的内存分配
当一个 CloseableHttpClient 的实例不再被使用,并且它的作用范围即将失效,和它相关的连接必须被关闭,关闭方法可以调用 CloseableHttpClient 的 close()方法。
Http 执行上下文
在 Http 请求执行的过程中,HttpClient 会自动添加下面的属性到 Http 上下文中:
HttpConnection 的实例,表示客户端与服务器之间的连接
HttpHost 的实例,表示要连接的目标服务器
HttpRoute 的实例,表示全部的连接路由
HttpRequest 的实例,表示 Http 请求。在执行上下文中,最终的 HttpRequest 对象会代表
http 消息的状态。Http/1.0 和 Http/1.1 都默认使用相对的 uri。但是如果使用了非隧道模式
的代理服务器,就会使用绝对路径的 uri。
HttpResponse 的实例,表示 Http 响应
java.lang.Boolean 对象,表示是否请求被成功的发送给目标服务器
RequestConfig 对象,表示 http request 的配置信息
java.util.List对象,表示 Http 响应中的所有重定向地址
异常处理
HttpClient 会被抛出两种类型的异常,一种是 java.io.IOException,当遇到 I/O 异常时抛出(socket超时,或者 socket 被重置);另一种是 HttpException,表示 Http 失败,如 Http 协议使用不正确。通常认为,I/O 错误时不致命、可修复的,而 Http 协议错误是致命了,不能自动修复的错误。
如果要自定义异常处理机制,我们需要实现 HttpRequestRetryHandler 接口。
终止请求
。通过 HttpClient 执行的 Http 请求,在任何状态下都能通过调用 HttpUriRequest 的 abort()方法来终止。这个方法是线程安全的,并且能在任何线程中调用。当 Http 请求被终止了,本线程(即使现在正在阻塞 I/O)也会通过抛出一个 InterruptedIOException 异常,来释放资源。
Http 协议拦截器
HTTP 协议拦截器是一种实现一个特定的方面的 HTTP 协议的代码程序。通常情况下,协议拦截器会将一个或多个头消息加入到接受或者发送的消息中。协议拦截器也可以操作消息的内容实体—消息内容的压缩/解压缩就是个很好的例子。
重定向处理
HttpClient 会自动处理所有类型的重定向,除了那些 Http 规范明确禁止的重定向。
使用自定义的重定向策略来放松 Http 规范对 Post 方法重定向的限制。
CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(redirectStrategy)
.build();
代码:
get请求
/**
* get请求方式
*/
public class DoGet {
public static void main(String[] args) {
//创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建httpGet
HttpGet httpGet = new HttpGet("https://www.baidu.com/");
//http响应
CloseableHttpResponse response = null;
try {
//执行请求
response = httpClient.execute(httpGet);
//判断返回状态是否ok
if (response.getStatusLine().getStatusCode() == 200) {
//获取实体
HttpEntity entity = response.getEntity();
//获取内容
String content = EntityUtils.toString(entity,"utf-8");
System.out.println("网页内容:"+content);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* get请求带参数方式
*/
public class DoGetParam {
public static void main(String[] args) throws Exception {
//创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
//定义请求的参数
URI uri = new URIBuilder("http://www.baidu.com/s").setParameter("wd", "java").build();
System.out.println(uri);
//创建HttpGet
HttpGet httpGet = new HttpGet(uri);
//执行请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
//如果返回状态为ok
if (response.getStatusLine().getStatusCode() == 200) {
//获取实体
HttpEntity entity = response.getEntity();
//获取网页内容
String content = EntityUtils.toString(entity, "utf-8");
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
httpClient.close();
}
}
}
post方式
/**
* post请求方式
*/
public class DoPost {
public static void main(String[] args) {
//创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建HttpPost
HttpPost httpPost = new HttpPost("http://www.oschina.net/");
//执行请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPost);
//判断返回状态是否ok
if (response.getStatusLine().getStatusCode() == 200) {
//打印网页内容
System.out.println(EntityUtils.toString(response.getEntity(),"utf-8"));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* post请求带参数方式
*/
public class DoPostParam {
public static void main(String[] args) throws Exception {
//创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建httpPost
HttpPost httpPost = new HttpPost("http://www.oschina.net/search");
// 设置2个post参数,一个是scope、一个是q
List<NameValuePair> params = new ArrayList<NameValuePair>();
//创建参数
NameValuePair params1 = new BasicNameValuePair("scope", "project");
NameValuePair params2 = new BasicNameValuePair("q", "java");
params.add(params1);
params.add(params2);
//构建一个form表单式的实体
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params);
//将实体添加到httpPost
httpPost.setEntity(formEntity);
//返回的响应
CloseableHttpResponse response = null;
try {
//执行请求
response = httpClient.execute(httpPost);
//判断返回状态是否ok
if (response.getStatusLine().getStatusCode() == 200) {
//网页内容
String content = EntityUtils.toString(response.getEntity(),"utf-8");
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
httpClient.close();
}
}
}
设置连接配置信息
/**
* 设置连接配置信息案例
*/
public class ResquestConfigDemo {
public static void main(String[] args) {
//创建httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建httpGet请求
HttpGet httpGet = new HttpGet("https://www.baidu.com/");
//构建请求配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000)//创建连接的最长时间
.setConnectionRequestTimeout(500)// 从连接池中获取到连接的最长时间
.setSocketTimeout(10000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true)// 提交请求前测试连接是否可用
.build();
//设置请求配置信息
httpGet.setConfig(config);
//执行请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
//判断返回状态是否ok
if (response.getStatusLine().getStatusCode() == 200) {
//获取网页内容
String content = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println(content);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
连接管理
PoolingHttpClientConnectionManager 维护的连接数在每个路由基础和总数上都有限制。默认,每个路由基础上的连接不超过 2 个,总连接数不能超过 20。在实际应用中,这个限制可能会太小了,尤其是当服务器也使用 Http 协议时。
即使 HttpClient 的实例是线程安全的,可以被多个线程共享访问,但是仍旧推荐每个线程都要有自己专用实例的 HttpContext。
HTTP 连接管理器
HttpClient 使用一个叫做 Http 连接管理器的特殊实体类来管理 Http 连接,这个实体类要实现 HttpClientConnectionManager 接口。
/**
* 连接池的使用
*/
public class HttpConnectManager {
public static void main(String[] args) {
//新建连接池
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
//设置最大的连接数
manager.setMaxTotal(200);
//设置每个主机的并发数
manager.setDefaultMaxPerRoute(20);
doGet(manager);
doGet(manager);
}
private static void doGet(PoolingHttpClientConnectionManager manager) {
//创建httpClient
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(manager).build();
//创建HttpGet请求
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
//返回响应
CloseableHttpResponse response = null;
try {
//执行请求
response = httpClient.execute(httpGet);
//获取内容
String content = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println(content);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁
// httpClient.close();
}
}
}
连接回收策略
这个监控线程可以周期性的调用 ClientConnectionManager 类的closeExpiredConnections()方法来关闭过期的连接,回收连接池中被关闭的连接。它也可以选择性的调用 ClientConnectionManager 类的 closeIdleConnections()方法来关闭一段时间内不活动的连接。
/**
* 模拟连接池关闭失效的连接
*/
public class ClientEvictExpiredConections {
public static void main(String[] args) {
//连接池
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
//设置最大连接数
manager.setMaxTotal(200);
//设置每个主机地址并发数
manager.setDefaultMaxPerRoute(20);
new IdleConnectionEvictor(manager).start();
}
public static class IdleConnectionEvictor extends Thread{
private final HttpClientConnectionManager manager;
private volatile boolean shutdown;
public IdleConnectionEvictor(HttpClientConnectionManager manager) {
this.manager = manager;
}
public void run(){
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
//关闭失效的连接
manager.closeExpiredConnections();
}
}
} catch (Exception e) {
//关闭连接
shutdown();
}
}
public void shutdown(){
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
}
连接存活策略
如果服务器返回的响应中没有包含 Keep-Alive 头消息,HttpClient 会认为这个连接可以永远保持。然而,很多服务器都会在不通知客户端的情况下,关闭一定时间内不活动的连接,来节省服务器资源。在某些情况下默认的策略显得太乐观,我们可能需要自定义连接存活策略。
socket 连接工厂
PlainConnectionSocketFactory 是默认的创建、初始化明文 socket(不加密)的工厂类。
HttpClient 使用 SSLSocketFactory 这个类实现安全 socket,SSLSocketFactory 实现了SSL/TLS 分层。请知晓,HttpClient 没有自定义任何加密算法。
SSLSocketFactory 允许用户高度定制。它可以接受 javax.net.ssl.SSLContext 这个类的实例作为参数,来创建自定义的 ssl 连接。
HttpClient 代理服务器配置
使用代理服务器最简单的方式就是,指定一个默认的 proxy 参数。
/**
* HttpClient代理设置
*/
public class HttpClientProxy {
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
//创建httpClient
httpClient = HttpClients.createDefault();
//创建httpClient
HttpGet httpGet = new HttpGet("http://www.tuicool.com/");
//设置代理
HttpHost proxy = new HttpHost("111.155.120.100", 8123);
RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
httpGet.setConfig(config);
//设置头信息,模拟火狐浏览器
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
response = httpClient.execute(httpGet);
//获取响应状态
System.out.println("Status: "+response.getStatusLine().getStatusCode());
//获取返回实体
HttpEntity entity = response.getEntity();
//获取网页内容
System.out.println("网页内容:" + EntityUtils.toString(entity, "utf-8"));
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Http状态管理
自定义 Cookie 策略
如果我们要自定义 Cookie 测试,就要自己实现 CookieSpec 接口,然后创建一个CookieSpecProvider 接口来新建、初始化自定义 CookieSpec 接口,最后把 CookieSpecProvider 注册到 HttpClient 中。一旦我们注册了自定义策略,就可以像其他标准策略一样使用了。
Cookie 持久化
HttpClient 可以使用任何存储方式的 cookie store,只要这个 cookie store 实现了 CookieStore 接口。默认的 CookieStore 通过 java.util.ArrayList 简单实现了 BasicCookieStore。存在在 BasicCookieStore中的 Cookie,当载体对象被当做垃圾回收掉后,就会丢失。如果必要,用户可以自己实现更为复杂的方式。
上传文件:
/**
* 模拟浏览器上传文件
*/
public class HttpClientUpload {
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
//创建httpClient
httpClient = HttpClients.createDefault();
//创建httpClient
HttpPost httpPost = new HttpPost("http://localhost/HttpClient/upload.action");
FileBody file = new FileBody(new File("E:/test.jpg"));
StringBody stringBody = new StringBody("test file",ContentType.TEXT_PLAIN);
HttpEntity entity = MultipartEntityBuilder.create().addPart("file",file).addPart("stringBody",stringBody).build();
httpPost.setEntity(entity);
System.out.println("executing request " + httpPost.getRequestLine());
//设置头信息
httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
response = httpClient.execute(httpPost);
//获取响应状态
System.out.println("Status: "+response.getStatusLine().getStatusCode());
//获取返回实体
HttpEntity httpEntity = response.getEntity();
if (httpEntity != null) {
//获取ContentType的值
System.out.println("Response content length" + httpEntity.getContentLength());
EntityUtils.consume(httpEntity);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
下载文件:
/**
* 模拟浏览器下载图片
*/
public class HttpClientDownload {
public static void main(String[] args) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
//创建httpClient
httpClient = HttpClients.createDefault();
//创建httpClient
HttpGet httpGet = new HttpGet("http://static0.tuicool.com/profile/4cb8a009b2ec_1479302811.jpg");
//设置头信息
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
response = httpClient.execute(httpGet);
//获取响应状态
System.out.println("Status: "+response.getStatusLine().getStatusCode());
//获取返回实体
HttpEntity entity = response.getEntity();
if (entity != null) {
//获取ContentType的值
String string = entity.getContentType().getValue();
System.out.println("ContentType: "+string);
InputStream is = entity.getContent();
FileUtils.copyInputStreamToFile(is, new File("e:/test.jpg"));
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}