以下是HttpClient工具类HttpClientUtil.java
package com.xpspeed.common.util;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ResourceLoader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
/**
* Created by doing on 14-1-3.
*/
public class HTTPClientUtil {
private static final Logger logger = LoggerFactory.getLogger(HTTPClientUtil.class);
//连接响应的超时时间,针对下载和视频播放
private static final long completeTimeOut = 60*60*1000l;
/**
* httpClient 只是作为一个公共的客户端,关键是设置连接池
* 使用时httpClient会自动从连接池获取一个连接,完毕后会自动释放链接
*/
private static CloseableHttpClient httpClient;
/**
* 不自动302跳转的客户端
*/
private static CloseableHttpClient httpClient_not_redirect;
private synchronized static void init(HttpClientConfig config) {
if(config == null) {
config = HttpClientConfigBuilder.create().buildDefault();
}
if (httpClient == null) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(config.connReqTimeout) //从连接池中获取连接的超时时间
//与服务器连接超时时间:httpclient会创建一个异步线程用以创建socket连接,此处设置该socket的连接超时时间
.setConnectTimeout(config.connTimeout)
.setSocketTimeout(config.socketTimeout) //socket读数据超时时间:从服务器获取响应数据的超时时间
.build();
httpClient = HttpClientBuilder.create()
.setMaxConnTotal(config.maxConnTotal) //连接池中最大连接数
/**
* 分配给同一个route(路由)最大的并发连接数。
* route:运行环境机器 到 目标机器的一条线路。
* 举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route。
*/
.setMaxConnPerRoute(config.maxConnPerRoute)
.setDefaultRequestConfig(requestConfig)
.build();
}
//初始化httpClient_not_redirect
if(httpClient_not_redirect == null) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(config.connReqTimeout) //从连接池中获取连接的超时时间
//与服务器连接超时时间:httpclient会创建一个异步线程用以创建socket连接,此处设置该socket的连接超时时间
.setConnectTimeout(config.connTimeout)
.setSocketTimeout(config.socketTimeout) //socket读数据超时时间:从服务器获取响应数据的超时时间
.setRedirectsEnabled(false) //禁止跳转
.build();
httpClient_not_redirect = HttpClientBuilder.create()
.setMaxConnTotal(config.maxConnTotal) //连接池中最大连接数
/**
* 分配给同一个route(路由)最大的并发连接数。
* route:运行环境机器 到 目标机器的一条线路。
* 举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route。
*/
.setMaxConnPerRoute(config.maxConnPerRoute)
.setDefaultRequestConfig(requestConfig)
.build();
}
}
/**
* 按给定的配置文件设置
* 初始化连接池,新建一个客户端httpClient
* properties file example(notice.properties):
* conn.request.timeout=5000
conn.timeout=3000
socket.timeout=5000
max.conn.total=500
max.conn.per.route=50
*
* @param properties
*/
public synchronized static void init(String properties) {
HttpClientConfig config = HttpClientConfigBuilder.create().build(properties);
init(config);
}
/**
* 默认设置
*/
public synchronized static void init() {
HttpClientConfig config = HttpClientConfigBuilder.create().buildDefault();
init(config);
}
public synchronized static void close() throws IOException {
if (httpClient != null) {
httpClient.close();
}
if(httpClient_not_redirect !=null ) {
httpClient_not_redirect.close();
}
}
public static String formPost(String url, Map<String, String> params) {
String result = null;
checkInit();
List<NameValuePair> formParams = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : params.entrySet()) {
formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
HttpPost httpPost = null;
try {
HttpEntity entity = new UrlEncodedFormEntity(formParams, "UTF-8");
httpPost = new HttpPost(url);
httpPost.setEntity(entity);
result = httpClient.execute(httpPost, new StringResponseHandler());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpPost此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpPost.releaseConnection();
}
return result;
}
public static String httpGet(String url) {
String result = null;
checkInit();
HttpGet httpGet = new HttpGet(url);
try {
result = httpClient.execute(httpGet, new StringResponseHandler());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpGet此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpGet.releaseConnection();
}
return result;
}
/**
* 获取302重定向的location
* @param url
* @return
*/
public static String get302Location(String url) {
HttpGet httpGet = null;
try {
httpGet = new HttpGet(url);
HttpResponse response = httpClient_not_redirect.execute(httpGet);
int responseCode = response.getStatusLine().getStatusCode();
if(responseCode == 302){
Header locationHeader = response.getFirstHeader("Location");
if (locationHeader != null) {
return locationHeader.getValue();
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpGet此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpGet.releaseConnection();
}
return null;
}
/**
* 发送请求,直到响应数据全部返还或者是超时
* @param url
* @return
* @throws IOException
*/
public static void httpGetUntilCompletedOrTimeout(String url) {
checkInit();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse httpResponse = null;
InputStream inputStream = null;
long beginTime = new Date().getTime();
try {
httpResponse = httpClient.execute(httpGet);
inputStream = httpResponse.getEntity().getContent();
byte[] bytes = new byte[2048];
while (true) {
if(inputStream.read(bytes) == -1) {
break;
}
if((new Date().getTime() - beginTime) >= completeTimeOut) {
break;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpGet此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpGet.releaseConnection();
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 发送请求,直到响应数据全部返还或者中断
* @param url
* @return
* @throws IOException
*/
public static void httpGetUntilCompletedOrInterrupted(String url){
checkInit();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse httpResponse = null;
InputStream inputStream = null;
try {
/**
* execute方法内部有两个步骤:连接目标地址,等待目标地址返回响应
* 所以可能抛出两个异常:连接超时异常ConnectTimeoutException,
* 读取数据超时异常(也即等待响应超时异常)SocketTimeoutException: Read timed out
*/
httpResponse = httpClient.execute(httpGet);
inputStream = httpResponse.getEntity().getContent();
byte[] bytes = new byte[2048];
while (true) {
if(inputStream.read(bytes) == -1) {
break;
}
if(Thread.interrupted()) { //判断是否中断且清除中断标志
break;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpGet此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpGet.releaseConnection();
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void httpGetForFile(String url, String newFilePath) {
checkInit();
InputStream inputStream = null;
OutputStream outputStream = null;
HttpGet httpGet = new HttpGet(url);
try {
CloseableHttpResponse response = httpClient.execute(httpGet);
inputStream = response.getEntity().getContent();
outputStream = new FileOutputStream(newFilePath);
IOUtils.copy(inputStream, outputStream);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
/**
* inputStream.close()并不会马上关闭输入流,而是等待输入流中的数据完全消耗之后才会关闭输入流,然后释放连接。
* 而如果先手动释放连接,因为连接断开了,所以执行inputStream.close()后就会马上关闭输入流(即立刻就没有流量了)。
* 为了可以立刻释放连接,断开流量,需要手动释放连接,然后关闭输入流。
* 不然的话很容易导致连接池中的连接长时间占用不能释放而导致其他线程获取连接超时。
* 注意:httpGet此时不一定已经获取连接,可能该httpGet一直在尝试获取连接而超时,即使没有获取到连接而尝试手动释放连接并不会有什么影响。
* 注意:释放连接并不是等于关闭连接!只是将该连接返还给连接池,让该连接处于空闲状态,但是该连接任然与服务端保持着连接。这就是长连接keepAlive的概念
* 这个长连接的保持时间可以在服务端设置,也可以在客户端设置,也可以在客户端直接关闭连接。
*/
httpGet.releaseConnection();
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
private static void checkInit() {
if (httpClient == null || httpClient_not_redirect == null) {
init();
}
}
private static class StringResponseHandler implements ResponseHandler<String> {
@Override
public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode < 300) {
HttpEntity entity = response.getEntity();
return entity == null ? null : EntityUtils.toString(entity, "UTF-8");
} else {
throw new ClientProtocolException("Unexpected response status: " + statusCode);
}
}
}
private static class HttpClientConfigBuilder {
private HttpClientConfigBuilder() {
}
public static HttpClientConfigBuilder create() {
return new HttpClientConfigBuilder();
}
HttpClientConfig build(String properties) {
Properties p = PropertiesUtils.loadProperties(ResourceLoader.CLASSPATH_URL_PREFIX + "/" + properties);
int connReqTimeout = Integer.valueOf(p.getProperty("conn.request.timeout", "5000"));
int connTimeout = Integer.valueOf(p.getProperty("conn.timeout", "3000"));
int socketTimeout = Integer.valueOf(p.getProperty("socket.timeout", "5000"));
int maxConnTotal = Integer.valueOf(p.getProperty("max.conn.total", "200"));
int maxConnPerRoute = Integer.valueOf(p.getProperty("max.conn.per.route", "20"));
return new HttpClientConfig(connReqTimeout, connTimeout, socketTimeout, maxConnTotal, maxConnPerRoute);
}
HttpClientConfig buildDefault() {
int connReqTimeout = 5000;
int connTimeout = 3000;
int socketTimeout = 5000;
int maxConnTotal = 200;
int maxConnPerRoute = 20;
return new HttpClientConfig(connReqTimeout, connTimeout, socketTimeout, maxConnTotal, maxConnPerRoute);
}
}
private static class HttpClientConfig {
int connReqTimeout;
int connTimeout;
int socketTimeout;
int maxConnTotal;
int maxConnPerRoute;
private HttpClientConfig(int connReqTimeout, int connTimeout, int socketTimeout, int maxConnTotal, int maxConnPerRoute) {
this.connReqTimeout = connReqTimeout;
this.connTimeout = connTimeout;
this.socketTimeout = socketTimeout;
this.maxConnTotal = maxConnTotal;
this.maxConnPerRoute = maxConnPerRoute;
}
}
}