1. httpclient
1.1. 基本介绍
HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。
HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口(基于Http协议的),提高了开发的效率,也方便提高代码的健壮性
注意:Commons HttpClient项目现在已经结束了,不再被开发。它已经被Apache HttpClient和HttpCore模块中的HttpComponents项目所取代,这些模块提供了更好的性能和更多的灵活性。详见:http://hc.apache.org/httpclient-legacy/index.html
1.2. 官网地址
官网地址
超文本传输协议(HTTP)可能是当今互联网上使用的最重要的协议。Web服务、支持网络的设备和网络计算的增长继续将HTTP协议的作用扩展到用户驱动的Web浏览器之外,同时增加了需要HTTP支持的应用程序的数量。
为扩展而设计的HttpComponents同时提供了对基本HTTP协议的健壮支持,任何构建感知HTTP的客户机和服务器应用程序(如web浏览器、web spider、HTTP代理、web服务传输库)的人都可能对它感兴趣
它由以下2个组件:
- HttpCore是一组低级HTTP传输组件,可用于以最小的占用空间构建自定义客户端和服务器端HTTP服务。HttpCore支持两种I/O模型:基于经典Java I/O的阻塞I/O模型和基于Java NIO的非阻塞事件驱动I/O模型。
- HttpClient是一个基于HttpCore的HTTP/1.1兼容的HTTP代理实现。它还为客户端身份验证、HTTP状态管理和HTTP连接管理提供可重用组件。HttpComponents Client是Commons HttpClient 3.x的继承者和替代者。强烈鼓励Commons HttpClient的用户升级
1.3. 特性
4.5.x特性
- 基于标准、纯净的java语言。实现了Http1.0和Http1.1
- 以可扩展的面向对象的结构实现了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
- 支持HTTPS协议。
- 通过Http代理建立透明的连接。
- 利用CONNECT方法通过Http代理建立隧道的https连接。
- Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos认证方案。
- 插件式的自定义认证方案。
- 便携可靠的套接字工厂使它更容易的使用第三方解决方案。
- 连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。
- 自动处理Set-Cookie中的Cookie。
- 插件式的自定义Cookie策略。
- Request的输出流可以避免流中内容直接缓冲到socket服务器。
- Response的输入流可以有效的从socket服务器直接读取相应内容。
- 在http1.0和http1.1中利用KeepAlive保持持久连接。
- 直接获取服务器发送的response code和 headers。
- 设置连接超时的能力。
- 实验性的支持http1.1 response caching。
- 源代码基于Apache License 可免费获取。
5.x 特性
2020年2月 httpclient从4.5直接升级了到5.0.
主要升级内容
1、支持HTTP/2
2、新的异步HTTP接口
3、重构reactor io模式,改进基于reactor 的NIO,使得性能和拓展性更好。
4、不论服务端是阻塞还是异步的实现,httpclient5均能支持服务端的过滤。例如横切协议(cross-cutting protocol)的握手,和用户认证授权。
5、支持reactive流的API
6、使用严格连接有限保证的方式重构连接池的实现。通过减少全局连接池的锁,连接池在高并发下获得更好的性能。
7、新的不严格连接有限保证连接池的实现。通过去除全局的连接池锁获得更高的性能。
8、更改包名
9、更改maven的groupId
2. 工具类源码
本文以httpclient 4.5.x, 将http请求的相同代码进行封装,简化代码,便捷使用。
2.1 使用示例
以第三方系统集成海豚调度系统为例,演示http请求工具类的使用
class HttpClientUtilsTest {
public static void main(String[] args) throws Exception {
String res = "";
Map<String, String> params = Maps.newHashMap();
params.put("userName", "admin");
params.put("userPassword", "dolphinscheduler123");
res = HttpClientUtils.post("http://172.25.xx.xx:12345/dolphinscheduler/login", params);
printMsg("login res:%s", res);
params.put("taskInstanceId", "703");
params.put("skipLineNum", "0");
params.put("limit", "1000");
Map<String, String> headers = Maps.newHashMap();
headers.put("token", "ca3e91749eee187fa9a797d92cf5cb6d");
res = HttpClientUtils.get("http://172.25.xx.xx:12345/dolphinscheduler/log/detail", params, headers);
printMsg("log detail res:%s", res);
}
private static void printMsg(String template, Object... args) {
System.out.println(String.format(template, args));
}
}
2.2. maven依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
2.3. 工具类源代码
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.SocketConfig;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Slf4j
class HttpClientUtils {
/**
* 从连接池中获取连接的超时时间(单位:ms)
*/
private static final int CONNECTION_REQUEST_TIMEOUT = 5000;
/**
* 与服务器连接的超时时间(单位:ms)
*/
private static final int CONNECTION_TIMEOUT = 5000;
/**
* 从服务器获取响应数据的超时时间(单位:ms)
*/
private static final int SOCKET_TIMEOUT = 10000;
/**
* 连接池的最大连接数
*/
private static final int MAX_CONN_TOTAL = 10;
/**
* 每个路由上的最大连接数
*/
private static final int MAX_CONN_PER_ROUTE = 5;
/**
* 用户代理配置
*/
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
public static final Charset UTF_8 = Charset.forName("UTF-8");
/**
* HttpClient对象
*/
private static CloseableHttpClient httpClient = null;
/**
* Connection配置对象
*/
private static ConnectionConfig connectionConfig = null;
/**
* Socket配置对象
*/
private static SocketConfig socketConfig = null;
/**
* Request配置对象
*/
private static RequestConfig requestConfig = null;
/**
* Cookie存储对象
*/
private static BasicCookieStore cookieStore = null;
static {
init();
}
/**
* 全局对象初始化
*/
private static void init() {
// 创建Connection配置对象
connectionConfig = ConnectionConfig.custom()
.setMalformedInputAction(CodingErrorAction.IGNORE)
.setUnmappableInputAction(CodingErrorAction.IGNORE)
.setCharset(UTF_8).build();
// 创建Socket配置对象
socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
// 创建Request配置对象
requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.setConnectTimeout(CONNECTION_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
// 创建Cookie存储对象(服务端返回的Cookie保存在CookieStore中,下次再访问时才会将CookieStore中的Cookie发送给服务端)
cookieStore = new BasicCookieStore();
// 创建HttpClient对象
httpClient = HttpClients.custom()
.setDefaultConnectionConfig(connectionConfig)
.setDefaultSocketConfig(socketConfig)
.setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(cookieStore)
.setUserAgent(USER_AGENT)
.setMaxConnTotal(MAX_CONN_TOTAL)
.setMaxConnPerRoute(MAX_CONN_PER_ROUTE)
.build();
}
/**
* post
*
* @param url
* @param jsonParam
* @return
* @throws Exception
*/
public static String post(String url, String jsonParam) throws IOException {
String result = null;
try {
// 创建HttpPost
HttpPost httpPost = new HttpPost(url);
// 设置请求数据
httpPost.setEntity(buildStringEntity(jsonParam, Consts.UTF_8));
// 发送Post请求并得到响应结果
HttpResponse httpResponse = httpClient.execute(httpPost);
StatusLine statusLine = httpResponse.getStatusLine();
HttpEntity httpEntity = httpResponse.getEntity();
if (statusLine == null) {
throw new ClientProtocolException("HttpResponse contains no StatusLine");
}
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
if (httpEntity == null) {
throw new ClientProtocolException("HttpResponse contains no HttpEntity");
}
result = EntityUtils.toString(httpEntity, "UTF-8");
} catch (Exception e) {
log.error("throw exception " + e.getMessage(), e);
throw e;
}
return result;
}
/**
* post请求(用于key-value格式的参数)
*
* @param url
* @param params
* @return
*/
public static String post(String url, Map<String, String> params) throws IOException {
String result = "";
try {
// 实例化HTTP方法
HttpPost httpPost = new HttpPost(url);
//设置参数
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (Iterator<String> iter = params.keySet().iterator(); iter.hasNext(); ) {
String name = iter.next();
String value = params.get(name);
nvps.add(new BasicNameValuePair(name, value));
}
httpPost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
// 发送Post请求并得到响应结果
HttpResponse httpResponse = httpClient.execute(httpPost);
StatusLine statusLine = httpResponse.getStatusLine();
HttpEntity httpEntity = httpResponse.getEntity();
if (statusLine == null) {
throw new ClientProtocolException("HttpResponse contains no StatusLine");
}
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
if (httpEntity == null) {
throw new ClientProtocolException("HttpResponse contains no HttpEntity");
}
result = EntityUtils.toString(httpEntity, "UTF-8");
} catch (Exception e) {
log.error("throw exception " + e.getMessage(), e);
throw e;
}
return result;
}
/**
* get
*
* @param url
* @param queryParams
* @return
* @throws Exception
*/
public static String get(String url, Map<String, String> queryParams, Map<String, String> headers) throws Exception {
String result = null;
try {
URIBuilder builder = new URIBuilder(url);
for (Map.Entry<String, String> entry :queryParams.entrySet()){
builder.setParameter(entry.getKey(), entry.getValue());
}
HttpGet httpGet = new HttpGet(builder.build());
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpGet.addHeader(entry.getKey(), entry.getValue());
}
HttpResponse response = httpClient.execute(httpGet);
StatusLine statusLine = response.getStatusLine();
HttpEntity httpEntity = response.getEntity();
if (statusLine == null) {
throw new ClientProtocolException("HttpResponse contains no StatusLine");
}
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
if (httpEntity == null) {
throw new ClientProtocolException("HttpResponse contains no HttpEntity");
}
result = EntityUtils.toString(httpEntity, "UTF-8");
} catch (Exception e) {
log.error("throw exception " + e.getMessage(), e);
throw e;
}
return result;
}
/**
* @param jsonParam
* @param charset
* @return
*/
private static StringEntity buildStringEntity(String jsonParam, Charset charset) {
/**
* jsonParam"",否则无法创建StringEntity。
* Json类型的请求体,必须传一个不为null的StringEntity给服务端。
* 如果jsonData为null或""时,则进行特殊处理。
*/
if (jsonParam == null || jsonParam.equals("")) {
jsonParam = "{}";
}
StringEntity stringEntity = new StringEntity(jsonParam, ContentType.APPLICATION_JSON);
if (charset != null) {
stringEntity.setContentEncoding(charset.name());
}
return stringEntity;
}
}