当前位置: 首页 > article >正文

【SpringBoot】29 基于HttpClient的Http工具类

Gitee仓库

https://gitee.com/Lin_DH/system

介绍

Http 协议是 Internet 上使用的最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 Http 协议来访问网络资源。虽然在 JDK 的 java net 包中已经提供了访问 Http 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且其支持 Http 协议最新的版本和建议。HttpClient 已经应用在很多的项目当中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTML Unit 都使用了 HttpClient。Commons HttpClient 项目现已终止,不在开发。其已被 Apache HttpComponents 项目里的 HttpClient 和 HttpCore 模块取代,它们都提供了更好的灵活性。

功能

1)实现了所有 Http 的方法(GET,POST,PUT,HEAD 等)
2)支持自动转向
3)支持 HTTPS 协议
4)支持代理服务器等

核心API

HttpClient:Http 客户端对象,使用该类型对象可发起 Http 请求
HttpClients:构建器,用于获取 HttpClient 对象
CloseableHttpClient:具体实现类,实现了 HttpClient 接口
HttpGet:Get 方式请求类型
HttpPost:Post方式请求类型

功能实现(传统HttpClient)

注:这是 HttpClient 传统的写法,封装好的 HttpUtil 在后文中。

依赖

pom.xml

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

数据接口

HelloController.java

package com.lm.system.controller;

import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @Author: DuHaoLin
 * @Date: 2024/7/26
 */

@RestController
public class HelloController {

    @GetMapping("hello")
    public String hello(@RequestParam("name") String name) {
        return "Hello " + name;
    }

    @PostMapping("postHello")
    public String postHello(@RequestBody Map<String, String> params) {
        return "postHello " + params;
    }

}

GET

实现步骤

使用 HttpClient 的 GET 方法需要以下六个步骤:
1)创建 HttpClient 实例
2)创建连接方法的实例,在 GetMethod 的构造函数中传入连接的地址
3)调用实例的 execute 方法来执行 GetMethod 实例
4)读取 response
5)释放连接,无论成功与否都需要释放连接
6)对得到后的内容进行处理

代码实现

HttpClientTest.java

package com.lm.system;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import java.io.IOException;

/**
 * @author DUHAOLIN
 * @date 2024/12/2
 */
public class HttpClientTest {

    private static final String GET_URL = "http://localhost:8888/hello";
   

    @Test
    public void getTest() {
        System.out.println("Get Method Result:" + doGet());
    }

    public String doGet() {
        //1.创建一个默认的实例
        CloseableHttpClient client = HttpClients.createDefault();
        String result = null;

        try {
            //2.创建一个HttpGet对象
            HttpGet get = new HttpGet(GET_URL + "?name=Joe");
            //3.执行GET请求并获取响应对象
            CloseableHttpResponse response = client.execute(get);

            try {
                //4.获取响应体
                HttpEntity entity = response.getEntity();
                //5.打印响应状态
                System.out.println("status code:" + response.getStatusLine());
                //6.打印响应长度和响应内容
                if (null != entity) {
                    result = EntityUtils.toString(entity);
                }
            } finally {
                //7.无论请求成功与否都要关闭resp
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //8.最终要关闭连接,释放资源
            try {
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

}

效果图

在这里插入图片描述

POST

实现步骤

使用 HttpClient 的 POST 方法需要以下六个步骤:
1)创建 HttpClient 实例
2)创建连接方法的实例,在 PostMethod 的构造函数中传入连接的地址和构建好的请求参数
3)调用实例的 execute 方法来执行 PostMethod 实例
4)读取 response
5)释放连接,无论成功与否都需要释放连接
6)对得到后的内容进行处理

代码实现

HttpClientTest.java

package com.lm.system;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import java.io.IOException;

/**
 * @author DUHAOLIN
 * @date 2024/12/2
 */
public class HttpClientTest {

    private static final String POST_URL = "http://localhost:8888/postHello";

    @Test
    public void postTest() {
        System.out.println("Post Method Result:" + doPost());
    }

    public String doPost() {
        //1.创建一个默认的实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        String result = null;

        try {
            //2.创建一个HttpPost对象
            HttpPost post = new HttpPost(POST_URL);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("name", "Tom");
            StringEntity entity = new StringEntity(jsonObject.toString());
            //3.设置编码、数据格式、请求参数
            entity.setContentEncoding("utf-8");
            entity.setContentType("application/json");
            post.setEntity(entity);
            //4.发送请求
            CloseableHttpResponse response = httpClient.execute(post);

            try {
                //5.解析处理结果
                System.out.println("status code:" + response.getStatusLine().getStatusCode());
                result = EntityUtils.toString(response.getEntity());
            } finally {
                //6.关闭连接,释放资源
                response.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return result;
    }

}

效果图

在这里插入图片描述

问题

HttpClient 已经可以使用了,为什么还要封装成工具类呢?
Java HttpClient 封装成工具类的主要原因是为了提高代码的复用性,减少代码冗余。
在 Java 开发中,HttpClient 是一个常用的工具类库,用于发送 HTTP 请求。虽然 Java 原生提供了 java.net 包来处理网络请求,但其功能相对有限,不够丰富和灵活。因此, HttpClient 库(如 Apache HttpClient)被广泛使用,其提供了更加丰富的功能和更好的性能。然而,直接使用 HttpClient 库可能会在项目中重复编写大量相似的代码,导致代码冗余和维护困难。

封装成工具类的优点

1)提高代码复用性:通过封装可以将 HttpClient 封装成一个工具类,在项目中可以直接调用该工具类中封装好的方法,而不需要重复编写相同的代码。
2)减少代码冗余:封装后的工具类可以统一管理 HttpClient 的使用,避免在不同地方重复编写相似代码,减少代码冗余。
3)便于维护和扩展:封装后的工具类使得代码更加模块化,便于后续的维护和功能扩展。如需要修改 HttpClient 的使用方式或添加性能,只需要修改工具类即可。

  • 支持多种请求方式:封装后的工具类可以自定义提供 GET、POST、PUT、DELETE 等多种 HTTP 请求方式。
  • 添加自定义头信息:可以在发送请求时添加自定义的头信息,满足不同的业务需求。
  • 处理响应内容:可以在处理响应内容,如解析 JSON、处理 Cookie 等。

HTTP工具类

代码实现

HttpClient.java

package com.lm.system.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author DUHAOLIN
 * @date 2024/12/2
 */
@Slf4j
public class HttpClient {

    //客户端从服务端读取数据的超时时间
    private static final int HTTP_TIMEOUT = 5000;
    //空闲的连接超时时间
    private static final int IDLE_TIMEOUT = 5000;
    //整个连接池连接的最大值
    private static final int HTTP_MAX_TOTAL = 10000;
    //客户端与服务器建立连接的超时时间
    private static final int HTTP_CON_TIMEOUT = 2000;
    //路由的默认最大连接
    private static final int HTTP_MAX_PERROUTE = 5000;
    //任务前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
    private static final int TASK_DELAY = 5000;
    //任务初始化延时
    private static final int TASK_INITIAL_DELAY = 5000;
    //客户端从连接池中获取连接的超时时间
    private static final int HTTP_CON_REQ_TIMEOUT = 1000;
    private static RequestConfig defaultRequestConfig = null;
    private static HttpRequestRetryHandler retryHandler = null;
    private static CloseableHttpClient defaultHttpClient = null;
    private static ScheduledExecutorService monitorExecutor = null;
    private static PoolingHttpClientConnectionManager connManager = null;

    private static final HttpClient httpClient = new HttpClient();

    public static HttpClient getInstance() {
        return httpClient;
    }

    private HttpClient() {
        //创建SSLConnectionSocketFactory
        SSLConnectionSocketFactory factory = getSSLConnectionSocketFactory();

        //创建连接池管理器
        connManager = createPoolConnectManager(factory);

        //设置Socket配置
        setSocketConfig();

        //设置获取连接超时时间,建立连接超时时间,从服务端读取数据的超时时间
        defaultRequestConfig = getRequestConfig();

        //请求失败时,进行请求重试
        retryHandler = retryHandler();

        //创建HttpClient实例
        defaultHttpClient = createHttpClient(factory);

        //开启线程监控,对异常和空闲线程进行关闭
        monitorExecutor = startUpThreadMonitor();

    }

    public CloseableHttpClient getHttpClient()  {
        return defaultHttpClient;
    }

    /**
     * 关闭连接池
     */
    public static void closeConnPool(){
        try {
            defaultHttpClient.close();
            connManager.close();
            monitorExecutor.shutdown();
            log.info("Close the thread pool");
        } catch (IOException e) {
            e.printStackTrace();
            log.error("Closing the thread pool failed", e);
        }
    }

    public RequestConfig getDefaultRequestConfig() {
        return defaultRequestConfig;
    }

    private SSLConnectionSocketFactory getSSLConnectionSocketFactory() {
        X509TrustManager manager = new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

        };

        SSLContext context = null;
        try {
            context = SSLContext.getInstance("TLS");
            //初始化上下文
            context.init(null, new TrustManager[] { manager }, null);
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }

        assert context != null;
        return new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);
    }

    private PoolingHttpClientConnectionManager createPoolConnectManager(SSLConnectionSocketFactory factory) {
        RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.create();
        Registry<ConnectionSocketFactory> registry = registryBuilder
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", factory)
                .build();
        return new PoolingHttpClientConnectionManager(registry);
    }

    private void setSocketConfig() {
        SocketConfig socketConfig = SocketConfig.custom()
                .setTcpNoDelay(true)
                .build();
        connManager.setDefaultSocketConfig(socketConfig);
        connManager.setMaxTotal(HTTP_MAX_TOTAL);
        connManager.setDefaultMaxPerRoute(HTTP_MAX_PERROUTE);
    }

    private RequestConfig getRequestConfig () {
        return RequestConfig.custom()
                .setSocketTimeout(HTTP_TIMEOUT)
                .setConnectTimeout(HTTP_CON_TIMEOUT)
                .setConnectionRequestTimeout(HTTP_CON_REQ_TIMEOUT)
                .build();
    }

    private HttpRequestRetryHandler retryHandler() {
        return (e, executionCount, httpContext) -> {
            //重试超过3次,放弃请求
            if (executionCount > 3) {
                log.error("retry has more than 3 time, give up request");
                return false;
            }
            //服务器没有响应,可能是服务器断开了连接,应该重试
            if (e instanceof NoHttpResponseException) {
                log.error("receive no response from server, retry");
                return true;
            }
            // SSL握手异常
            if (e instanceof SSLHandshakeException){

                log.error("SSL hand shake exception");
                return false;
            }
            //超时
            if (e instanceof InterruptedIOException){
                log.error("InterruptedIOException");
                return false;
            }
            // 服务器不可达
            if (e instanceof UnknownHostException){
                log.error("server host unknown");
                return false;
            }
            if (e instanceof SSLException){
                log.error("SSLException");
                return false;
            }
            HttpClientContext context = HttpClientContext.adapt(httpContext);
            HttpRequest request = context.getRequest();
            //如果请求不是关闭连接的请求
            return !(request instanceof HttpEntityEnclosingRequest);
        };




    }

    private CloseableHttpClient createHttpClient(SSLConnectionSocketFactory factory) {
        CloseableHttpClient httpClient = HttpClients.custom()
//                .setRetryHandler(retryHandler)
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(defaultRequestConfig)
//                .setSSLSocketFactory(factory)
                .build();
        log.info("HttpClient Build");
        return httpClient;
    }

    private ScheduledExecutorService startUpThreadMonitor() {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                try {
                    //关闭异常连接
                    connManager.closeExpiredConnections();
                    //关闭5s空闲的连接
                    connManager.closeIdleConnections(IDLE_TIMEOUT, TimeUnit.MILLISECONDS);
                    log.debug("close expired and idle for over IDLE_TIMEOUT connection");
                } catch (Exception e) {
                    log.error("close expired or idle for over IDLE_TIMEOUT connection  fail", e);
                }
            }
        }, TASK_INITIAL_DELAY, TASK_DELAY, TimeUnit.MICROSECONDS);

        return executor;
    }

}

HttpUtil.java

package com.lm.system.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

/**
 * @author DUHAOLIN
 * @date 2024/12/2
 */
public class HttpUtil {

    private static final String JSON_FORMAT = "application/json";
    private static final String UTF8_CHARSET = "utf-8";
    private static final RequestConfig DEFAULT_REQUEST_CONFIG = HttpClient.getInstance().getDefaultRequestConfig();


    public static HttpEntity doGet(String url) {
        return send(url, HttpClient.getInstance());
    }

    private static HttpEntity send(String url, HttpClient httpClient) {
        HttpGet get = new HttpGet(url);

        try {
            get.setConfig(DEFAULT_REQUEST_CONFIG);
            HttpResponse response = httpClient.getHttpClient().execute(get);
            return response.getEntity();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String doHttpPost(String url) {
        return sendData(url, createSSLInsecureClient(), UTF8_CHARSET);
    }

    public static String doHttpPost(String url, String data) {
        return sendData(url, data, createSSLInsecureClient(), UTF8_CHARSET);
    }

    public static String doHttpPost(String url, String data, String encoding) {
        return sendData(url, data, createSSLInsecureClient(), encoding);
    }

    public static String doHttpPost(String url, String data, String encoding, String contentType) {
        return sendData(url, data, createSSLInsecureClient(), encoding, contentType);
    }

    public static String doHttpsPost(String data, String url, String certAddress, String mchId, String TLSVersion, String encoding) {
        return sendData(url, data, getCAHttpClient(mchId, certAddress, TLSVersion), encoding);
    }

    private static String sendData(String url, String data, CloseableHttpClient httpClient, String encoding) {
        return sendData(url, data, httpClient, encoding, JSON_FORMAT);
    }

    private static CloseableHttpClient getCAHttpClient(String mchId, String certAddress, String TLSVersion) {
        if (!StringUtils.hasText(TLSVersion)) {
            TLSVersion = "TLSv1";
        }
        CloseableHttpClient httpClient = null;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            try (FileInputStream inputStream = new FileInputStream(certAddress)) {
                keyStore.load(inputStream, mchId.toCharArray());
            }
            SSLContext context = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, mchId.toCharArray())
                    .build();
            SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(
                    context,
                    new String[] { TLSVersion },
                    null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier()
            );
            httpClient = HttpClients.custom()
                    .setSSLSocketFactory(factory)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return httpClient;
    }

    private static String sendData(String url, String data, CloseableHttpClient httpClient, String encoding, String contentType) {
        HttpPost post = new HttpPost(url);
        String result = null;
        try {
            post.setConfig(DEFAULT_REQUEST_CONFIG);
            StringEntity entity = new StringEntity(data, UTF8_CHARSET);
            entity.setContentEncoding(UTF8_CHARSET);
            entity.setContentType(contentType);
            post.setEntity(entity);
            HttpResponse response = httpClient.execute(post);
            HttpEntity returnEntity = response.getEntity();
            result = EntityUtils.toString(returnEntity, encoding);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    private static CloseableHttpClient createSSLInsecureClient() {
        try {
            SSLContext context = new SSLContextBuilder()
                    .loadTrustMaterial(null, (x509Certificates, s) -> true)
                    .build();
            SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(context, new NoopHostnameVerifier());
            return HttpClients.custom()
                    .setSSLSocketFactory(factory)
                    .build();
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            e.printStackTrace();
        }
        return HttpClients.createDefault();
    }

    private static String sendData(String url, CloseableHttpClient httpClient, String encoding) {
        HttpPost post = new HttpPost(url);
        String result;
        try {
            post.setConfig(DEFAULT_REQUEST_CONFIG);
            HttpResponse response = httpClient.execute(post);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, encoding);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("请求异常", e);
        }
        return result;
    }

}

HttpTest.java

package com.lm.system;

import com.alibaba.fastjson.JSONObject;
import com.lm.system.util.HttpUtil;
import org.apache.http.HttpEntity;
import org.junit.Test;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.stream.Collectors;


/**
 * @author DUHAOLIN
 * @date 2024/12/2
 */
public class HttpTest {

    @Test
    public void test01() throws Exception {
        String url = "http://localhost:8888/hello";
        String params = "?name=Tom";
        HttpEntity entity = HttpUtil.doGet(url + params);
        String result = new BufferedReader(
                new InputStreamReader(entity.getContent())
            ).lines().collect(Collectors.joining("\n"));
        System.out.println("result:" + result);
    }

    @Test
    public void test02() {
        String url = "http://localhost:8888/postHello";
        JSONObject json = new JSONObject();
        json.put("name", "Alice");
        String data = HttpUtil.doHttpPost(url, json.toString());
        System.out.println("data:" + data);
    }

}

效果图

在这里插入图片描述

项目结构图

在这里插入图片描述


http://www.kler.cn/a/421229.html

相关文章:

  • Ubuntu 安装Ansible ansible.cfg配置文件生成
  • Linux命令行解释器的模拟实现
  • flink-connector-mysql-cdc:03 mysql-cdc常见问题汇总
  • 基于hexo框架的博客搭建流程
  • linux 压缩命令,压缩a目录,但是不压缩a目录下的b目录,zip命令
  • 【C语言】递归的内存占用过程
  • [自然语言处理] NLP-RNN及其变体-干货
  • Python 网络爬虫入门全知道
  • 分布式推理框架 xDit
  • 【threejs】实现不同动画的播放和平滑切换
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验三----学校选址与路径规划(超超超详细!!!)
  • <数据集>路面坑洼识别数据集<目标检测>
  • 王道操作系统目录以及学习感受和总结
  • 《向量数据库指南》——Florence:多模态应用的新基石!
  • 2024年大热,Access平替升级方案,也适合Excel用户
  • Vuex的理解及使用场景
  • 南昌榉之乡托养机构解读:自闭症与看电视并无必然联系
  • 大数据新视界 -- Hive 数据湖架构中的角色与应用(上)(25 / 30)
  • CTF之WEB(sqlmap tamper 参数)
  • 零基础快速掌握——【c语言基础】数组的操作,冒泡排序,选择排序
  • 基于单片机的智能宠物喂食系统设计
  • 华为HarmonyOS 让应用快速拥有账号能力 -- 2 获取用户头像昵称
  • 服务器数据恢复—EVA存储硬盘磁头和盘片损坏离线的数据恢复案例
  • PH热榜 | 2024-12-03
  • taro小程序马甲包插件
  • 链表的分类以及双向链表的实现