通用HttpClient请求工具类

前言

现在大多数Java项目开发中,经常都用通过HTTP协议来调用网络资源数据(1、爬虫爬取网页数据;2、请求第三方系统进行数据交互等),虽然JDK8及以前的版本,也提供了响应的请求工具包,但是使用起来很不灵活,所以大多数都是采用Apache的HttpClient包来封装自己的请求工具类,方便整个项目开发使用。

使用流程

1、引入maven依赖

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

 2、HttpClient使用

2.1 创建请求客户端对象

/*
 1、创建自定义请求客户端构建对象
 */
HttpClientBuilder httpClientBuilder = HttpClients.custom();
// 创建连接池,并对连接池进行设置后赋值给请求对象构造器
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
httpClientBuilder.setConnectionManager(cm);
CloseableHttpClient build = httpClientBuilder.build();
/*
 2、创建默认的请求客户端构建对象
 */
CloseableHttpClient request2 = HttpClients.createDefault();

2.2 创建连接池管理对象

// 创建请求连接池管理,可创建空的,也可以创建带注册对象(注册对象示例:https的连接认证注册)
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 设置连接池最大连接数(设置时可以设置部署机器的CPU线程数)
cm.setMaxTotal(8);
// 设置每个路由最大默认连接数(可以认为一个域名就是路由)
cm.setDefaultMaxPerRoute(8);

2.3 请求方式对象

// get请求对象
HttpGet httpGet = new HttpGet(url);
// post请求对象
HttpPost httpPost = new HttpPost(url);

2.4 请求头设置

// 设置用户代理为浏览器
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38");
// 设置请求的数据类型
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");

2.5 参数设置

注意:随着请求方式的不同,传参方式也大不相同

/*
 1、get请求
 */
String url = "url";
Map<String, String> params = new HashMap<>();
params.put("username", "chris");
URIBuilder uriBuilder = new URIBuilder(url);
if (params!=null) { // 判空操作要有,避免空指针异常
    for (String paramKey : params.keySet()) {
        uriBuilder.addParameter(paramKey, params.get(paramKey));
    }
}
HttpGet httpGet = new HttpGet(uriBuilder.build());
/*
 2、post请求(表单形式:application/x-www-form-urlencoded)
 */
String url = "url";
Map<String, String> params = new HashMap<>();
params.put("username", "chris");
HttpPost httpPost = new HttpPost(url);
if (params!=null) { // 判空操作要有,避免空指针异常
    List<NameValuePair> nvList = new ArrayList<>(params.size());
    NameValuePair nv = null;
    for (String paramKey : params.keySet()) {
     // 构建参数键值对象
        nv = new BasicNameValuePair(paramKey, params.get(paramKey));
        nvList.add(nv);
    }
    // 传送参数的对象
    HttpEntity paramsEntity = new UrlEncodedFormEntity(nvList, StandardCharsets.UTF_8);
    // 设置参数
    httpPost.setEntity(paramsEntity);
}
/*
 3、post请求(json形式:application/json)
 */
String url = "url";
Map<String, String> params = new HashMap<>();
params.put("username", "chris");
HttpPost httpPost = new HttpPost(url);
if (params!=null) { // 判空操作要有,避免空指针异常
    // 需要将请求对象转换为json字符串形式
    HttpEntity paramEntity = new StringEntity(JSON.toJSONString(params), StandardCharsets.UTF_8);
    httpPost.setEntity(paramEntity);
}

2.6 执行请求并处理返回对象

CloseableHttpResponse response = null;
try {
    // 执行请求
    response = closeableHttpClient.execute(httpPost);
    int statusCode = response.getStatusLine().getStatusCode();
    // 判断请求响应是否成功
    if (statusCode== HttpStatus.SC_OK) { 
        // 处理响应数据并返回
        HttpEntity entity = response.getEntity();
        return EntityUtils.toString(entity, StandardCharsets.UTF_8);
    } else {
        log.error("请求地址({})失败:{}", url, statusCode);
    }
} catch (IOException e) {
    log.error("请求地址({})失败", url, e);
    throw new RuntimeException("请求地址("+url+")失败");
} finally { // 确认数据消费并关闭http响应对象
    HttpClientUtils.closeQuietly(response);
}

3、书写请求工具类

/**
 * http请求工具类
 */
@Slf4j
public class HttpUtil {

    /**
     * 请求连接构造对象
     */
    private static final HttpClientBuilder httpClientBuilder = HttpClients.custom();

    /**
     * 连接池最大连接数
     */
    private static final int MAX_TOTAL = 8;

    /**
     * 每个路由最大默认连接数
     */
    private static final int DEFAULT_MAX_RER_ROUTE = 8;

    /**
     * 获取连接获取超时时间
     */
    private static final int CONNECTION_REQUEST_TIMEOUT = 2000;

    /**
     * 连接超时时间
     */
    private static final int CONNECTION_TIMEOUT = 2000;

    /**
     * 数据响应超时时间
     */
    private static final int SOCKET_TIMEOUT = 10000;



    static {
        /*
         1、绕开不安全的https请求的证书验证(不需要可以注释,然后使用空参数的PoolingHttpClientConnectionManager构造连接池管理对象)
         */
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", trustHttpsCertificates())
                .build();

        /*
         2、创建请求连接池管理
         */
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        // 设置连接池最大连接数
        cm.setMaxTotal(MAX_TOTAL);
        // 设置每个路由最大默认连接数
        cm.setDefaultMaxPerRoute(DEFAULT_MAX_RER_ROUTE);
        httpClientBuilder.setConnectionManager(cm);

        /*
        3、设置默认请求配置
         */
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT) // 设置获取连接获取超时时间
                .setConnectTimeout(CONNECTION_TIMEOUT) // 设置连接超时时间
                .setSocketTimeout(SOCKET_TIMEOUT) // 设置数据响应超时时间
                .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
    }


    /**
     *  执行get请求(网页)
     * @param url 请求地址(含有特殊符号需要URLEncoder编码)
     * @param headers 请求头参数
     * @return 响应数据
     */
    public static String getPage(String url, Map<String, String> headers) {

        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        HttpGet httpGet = new HttpGet(url);

        // 请求头设置,如果常用的请求头设置,也可以写死,特殊的请求才传入
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38");
        if (headers != null) {
            for (String headerKey : headers.keySet()) {
                httpGet.setHeader(headerKey, headers.get(headerKey));
            }
        }

        CloseableHttpResponse response = null;
        try {
            response = closeableHttpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode== HttpStatus.SC_OK) { // 请求响应成功
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity, StandardCharsets.UTF_8);
            } else {
                log.error("请求地址({})失败:{}", url, statusCode);
            }
        } catch (Exception e) {
            log.error("请求地址({})失败", url, e);
            throw new RuntimeException("请求地址("+url+")失败");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return null;
    }

    /**
     *  执行post请求(form表单)
     * @param url 请求地址
     * @param headers 请求头参数
     * @return 响应数据
     */
    public static String postForm(String url, Map<String, String> headers, Map<String, String> params) {
        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        HttpPost httpPost = new HttpPost(url);
         // 请求头设置,如果常用的请求头设置,也可以写死,特殊的请求才传入
        httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38");
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        if (headers != null) {
            for (String headerKey : headers.keySet()) {
                httpPost.setHeader(headerKey, headers.get(headerKey));
            }
        }

        // 设置请求参数
        if (params!=null) {
            List<NameValuePair> nvList = new ArrayList<>(params.size());
            for (String paramKey : params.keySet()) {
                NameValuePair nv = new BasicNameValuePair(paramKey, params.get(paramKey));
                nvList.add(nv);
            }
            HttpEntity paramsEntity = new UrlEncodedFormEntity(nvList, StandardCharsets.UTF_8);
            httpPost.setEntity(paramsEntity);
        }

        CloseableHttpResponse response = null;
        try {
            response = closeableHttpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode== HttpStatus.SC_OK) { // 请求响应成功
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity, StandardCharsets.UTF_8);
            } else {
                log.error("请求地址({})失败:{}", url, statusCode);
            }
        } catch (IOException e) {
            log.error("请求地址({})失败", url, e);
            throw new RuntimeException("请求地址("+url+")失败");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return null;
    }

    /**
     *  执行post请求(接口)
     * @param url 请求地址
     * @param headers 请求头参数
     * @return 响应数据
     */
    public static String getJson(String url, Map<String, String> headers) {
        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        HttpGet httpGet = new HttpGet(url);
        // 请求头设置,如果常用的请求头设置,也可以写死,特殊的请求才传入
        if (headers != null) {
            for (String headerKey : headers.keySet()) {
                httpGet.setHeader(headerKey, headers.get(headerKey));
            }
        }
        CloseableHttpResponse response = null;
        try {
            response = closeableHttpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode== HttpStatus.SC_OK) { // 请求响应成功
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity, StandardCharsets.UTF_8);
            } else {
                log.error("请求地址({})失败:{}", url, statusCode);
            }
        } catch (IOException e) {
            log.error("请求地址({})失败", url, e);
            throw new RuntimeException("请求地址("+url+")失败");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return null;
    }

    /**
     *  执行post请求(接口)
     * @param url 请求地址
     * @param headers 请求头参数
     * @return 响应数据
     */
    public static String postJson(String url, Map<String, String> headers, Map<String, String> params) {
        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        HttpPost httpPost = new HttpPost(url);
        // 请求头设置,如果常用的请求头设置,也可以写死,特殊的请求才传入
        httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
        if (headers != null) {
            for (String headerKey : headers.keySet()) {
                httpPost.setHeader(headerKey, headers.get(headerKey));
            }
        }
        if (params!=null) {
            HttpEntity paramEntity = new StringEntity(JSON.toJSONString(params), StandardCharsets.UTF_8);
            httpPost.setEntity(paramEntity);
        }

        CloseableHttpResponse response = null;
        try {
            response = closeableHttpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode== HttpStatus.SC_OK) { // 请求响应成功
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity, StandardCharsets.UTF_8);
            } else {
                log.error("请求地址({})失败:{}", url, statusCode);
            }
        } catch (IOException e) {
            log.error("请求地址({})失败", url, e);
            throw new RuntimeException("请求地址("+url+")失败");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return null;
    }

    /**
     * 构建https安全连接工厂
     * @return 安全连接工厂
     */
    private static ConnectionSocketFactory trustHttpsCertificates() {
        SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
        try {
            sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    return true;
                }
            });
            SSLContext sslContext = sslContextBuilder.build();
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
                    new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}, // 支持的https安全认证协议
                    null, NoopHostnameVerifier.INSTANCE);
            return sslConnectionSocketFactory;
        } catch (Exception e) {
            log.error("构建安全连接工厂失败", e);
            throw new RuntimeException("构建安全连接工厂失败");
        }
    }
}

4、额外说明

  • 项目中HttpClient基本最好构建请求连接池进行请求,提升性能,加快请求速度;
  • 如果你的应用不请求不信任的https连接,则不需要绕过https安全认证(不需要使用到trustHttpsCertificates方法);
  • 项目请求在工具类一般可以确定请求头,可以写死,从而少传入一个参数;(特殊要求传入的请求才保留该参数设置)
  • 每次请求后一定要确认消费和关闭响应;(HttpClientUtils.closeQuietly(response))
  • 部分爬虫需要设置代理,使用示例如下:
HttpGet httpGet = new HttpGet(url);
HttpHost proxy = new HttpHost("49.70.17.48", 8888);
RequestConfig config = RequestConfig.custom()
        .setProxy(proxy) //设置代理
        .setConnectTimeout(2000) // 设置HTTP连接超时时间
        .setSocketTimeout(3000)  // 设置数据响应超时时间
        .setConnectionRequestTimeout(2000) // 设置从连接池获取连接的超时时间
        .build();
httpGet.setConfig(config); 

  成功 = 正确的选择 + 实际的行动 + 长期的坚持;

原文地址:https://www.cnblogs.com/gangbalei/p/15399471.html