Apache HttpClient 4.5 长连接池及 Fluent API 介绍

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


猜你喜欢

转载自blog.csdn.net/vipshop_fin_dev/article/details/80561976