Apache HttpClient 4.5 长连接池及 Fluent API 介绍
最近的项目中用到了 Apache HttpComponents 项目中 HttpClient 4.5 长连接池的功能,再研究官方标准写法的同时,突然看到了Apache HttpClient Fluent API,相比之前 HttpClient 的传统书写方式,Fluent API 方便了很多。
在介绍之前先准备一下实例代码中用到的工具类,工具类中添加了 HttpClient 连接的创建方法和构建表单方式的 Http 实体内容的方法:
package cn.cokolin;
import org.apache.commons.collections.MapUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Http 连接池帮助类
*
* @author chunlin.qiu
* @see org.apache.http.client.fluent.Request
* @see org.apache.http.client.fluent.Executor
*/
public class HttpPoolHelper {
public static CloseableHttpClient buildPoolHttpClient(int liveSeconds, int maxPerRoute, int maxTotal) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(liveSeconds, TimeUnit.SECONDS);
cm.setValidateAfterInactivity(2_000);// 每隔 2 秒才检查连接的有效性
cm.setDefaultMaxPerRoute(maxPerRoute);// 默认最大预处理路由,如果只有一个路由,可以等于 maxTotal
cm.setMaxTotal(maxTotal);//连接池最大值
return HttpClients.custom().setConnectionManager(cm).build();
}
public static UrlEncodedFormEntity buildFormEntity(Map<String, String> params) {
return new UrlEncodedFormEntity(paramToPair(params), StandardCharsets.UTF_8);
}
/**
* @see org.apache.http.message.BasicNameValuePair
*/
public static List<NameValuePair> paramToPair(Map<String, String> params) {
List<NameValuePair> result = Collections.emptyList();
if (MapUtils.isNotEmpty(params)) {
result = new ArrayList<>(params.size());
for (Map.Entry<String, String> entry : params.entrySet()) {
String value = entry.getValue();
if (value != null) {
String key = entry.getKey();
result.add(new BasicNameValuePair(key, value));
}
}
}
return result;
}
}
如果没有 HTTP 代理、自定义 HTTP 头、自定义 HTTP Cookies、HTTP 访问验证这些东西的话,HttpClient 的长连接的实现是非常简便的。HttpClient 的代码普遍使用了类似工厂模式的设计模式,代码可读性不错。下面就讲讲 Fluent API 的用法。
先提供一个没有 Fluent API 的 Http Post 的写法:
public static String httpPost(String uri, Map<String, String> params, int timeoutMills) throws IOException {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(uri);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(timeoutMills) // 比 Fluent API 可以多配置一个 timeout
.setConnectTimeout(timeoutMills)
.setSocketTimeout(timeoutMills)
.build();
httpPost.setConfig(requestConfig);
httpPost.setEntity(HttpPoolHelper.buildFormEntity(params));
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
}
}
}
多亏了 Java 1.7 的 try-with-resource 语法,让这段代码可以也可以写得非常短,但里面嵌套了两层的 try 块。
另外来看看 Fluent API 的链式写法:
public static String fluentPost(String uri, Map<String, String> params, int timeoutMills) throws IOException {
return Request.Post(uri) //
.socketTimeout(timeoutMills) //
.connectTimeout(timeoutMills) //
.body(HttpPoolHelper.buildFormEntity(params)) //
.execute().returnContent().asString(StandardCharsets.UTF_8);
}
使用 Fluent API 后实际可以一行代码搞定这个 HttpPost 请求。
另外来段非 Fluent API 下 Http 连接池的 POST 写法:
private static CloseableHttpClient httpClient;
static {
httpClient = HttpPoolHelper.buildPoolHttpClient(600, 100, 100);
}
public static String httpPoolPost(String uri, Map<String, String> params, int timeoutMills) throws IOException {
HttpPost httpPost = new HttpPost(uri);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(timeoutMills)
.setSocketTimeout(timeoutMills)
.build();
httpPost.setConfig(requestConfig);
httpPost.setEntity(HttpPoolHelper.buildFormEntity(params));
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
}
}
Fluent API 下 Http 连接池的POST 写法:
private static Executor executor;
static {
executor = Executor.newInstance(httpClient);
}
public static String fluentPoolPost(String uri, Map<String, String> params, int timeoutMills) throws IOException {
Request body = Request.Post(uri)
.socketTimeout(timeoutMills)
.connectTimeout(timeoutMills)
.body(HttpPoolHelper.buildFormEntity(params));
return executor.execute(body).returnContent().asString(StandardCharsets.UTF_8);
}
Fluent API 下也是两步搞定,不过这一块跟非连接池下的代码很类似,这也说明了 HttpClient 的封装非常不错。
总结,本文主要是为了备注一下 HttpClient 长连接的写法和 Fluent API 的用途,见解非常微浅,请大家见谅。
还有 Fluent API 跟 Spring RestTemplate API 很相似,在不使用 Http 连接池的情况下,可能 Spring RestTemplate API 更实惠。
write by chunlin.qiu