基于SpringBoot的httpclient实现(高级版)

news2025/1/26 15:04:12

SpringBoot集成Apache HttpClient

一、SpringBoot的HttpClient简单实现

SpringBoot的HttpClient简单实现

二、覆盖绝大多数场景的封装

1.添加依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.18</version>
        </dependency>

        <!-- springboot中包含了httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.14</version>
        </dependency>
        
		<dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>2.0.44</version>
            </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

2.代码实现

  • 定义配置类
package com.zzc.component.http;

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;
import java.util.Map;

/*
 httpclient:
 charset: UTF-8
 conn-max-total: 300
 max-per-route: 100
 retry-num: 1
 connect-timeout: 30000 # 单位:毫秒
 read-timeout: 15000   # 单位:毫秒
 request-timeout: 200  # 单位:毫秒
 keep-alive-time: 60   # 单位:秒

 # 如果请求目标地址单独配置可长链接保持时间,使用该配置
 keep-alive-target-host:
 example.com: 120      # example.com 的保持活动时间为 120 秒
 api.example.org: 300  # api.example.org 的保持活动时间为 300 秒
 www.baidu.com: 60
 localhost: 60

 # http 请求 header
 headers:
 Content-Type: application/json
 target-hosts:
 - domain: http://www.baidu.com
 keep-alive-time: 120
 - domain: http://localhost:19999
 connect-timeout: 100
 read-timeout: 100
 request-timeout: 100
 */
@Data
@ToString
@ConfigurationProperties(prefix = "httpclient")
public class HttpClientProperties {

    private String charset = "UTF-8";

    /**
     * 总链接数
     */
    private Integer connMaxTotal = 3000;

    /**
     * 并发数量
     */
    private Integer maxPerRoute = 1200;

    /**
     * 重试次数
     */
    private Integer retryNum = 1;

    /**
     * 链接超时
     */
    private Integer connectTimeout = 30000;

    /**
     * 读写超时
     */
    private Integer readTimeout = 15000;

    /**
     * 链接不够用的等待时间,不宜过长,必须设置
     */
    private Integer requestTimeout = 200;

    /**
     * 默认链接保持时间,单位 秒
     */
    private Integer keepAliveTime = 60;

    /**
     * 如果请求目标地址单独配置可长链接保持时间,使用该配置
     */
    private Map<String, Integer> keepAliveTargetHost;

    /**
     * http请求header
     */
    private Map<String, String> headers;

    /**
     * 指定目标地址的配置一些基础配置
     */
    private List<TargetHostConfig> targetHosts;

    @Data
    @ToString
    public static class TargetHostConfig {

        /**
         * 指定域名
         */
        private String domain;

        /**
         * 默认链接保持时间,单位 秒
         */
        private Integer keepAliveTime;

        /**
         * 链接超时
         */
        private Integer connectTimeout;

        /**
         * 最大并发数量
         */
        private Integer maxPerRoute;

        /**
         * 读写超时
         */
        private Integer readTimeout;

        /**
         * 链接不够用的等待时间,不宜过长
         */
        private Integer requestTimeout;

        /**
         * http请求header
         */
        private Map<String, String> headers;

        public String getHostname() {
            if (domain != null && !domain.trim().isEmpty()) {
                if (domain.startsWith("http://")) {
                    return domain.replace("http://", "");
                } else if (domain.startsWith("https://")) {
                    return domain.replace("https://", "");
                } else {
                    return domain;
                }
            }
            return null;
        }
    }

}

    • 封装HttpClientStatus状态码枚举,避免框架替换的时候需要整改整个项目
package com.zzc.component.http;

import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;

public enum HttpClientStatus {

// 1xx Informational

    /**
     * 客户端应当继续发送请求。这个代码只允许用在头部字段有 Expect 的情况下
     * {@code 100 Continue}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.2.1">HTTP/1.1: Semantics and Content, section 6.2.1</a>
     */
    CONTINUE(100, Series.INFORMATIONAL, "Continue"),

    /**
     * 服务器已经理解了客户端的请求,并将通过升级协议来完成它(例如从 HTTP 切换到 WebSocket)。
     * {@code 101 Switching Protocols}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.2.2">HTTP/1.1: Semantics and Content, section 6.2.2</a>
     */
    SWITCHING_PROTOCOLS(101, Series.INFORMATIONAL, "Switching Protocols"),

    /**
     * 服务器已经收到了请求,但尚未处理完毕,不过最终会处理完。
     * {@code 102 Processing}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2518#section-10.1">WebDAV</a>
     */
    PROCESSING(102, Series.INFORMATIONAL, "Processing"),

    /**
     * {@code 103 Checkpoint}.
     *
     * @see <a href="https://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal">A proposal for supporting
     * resumable POST/PUT HTTP requests in HTTP/1.0</a>
     */
    CHECKPOINT(103, Series.INFORMATIONAL, "Checkpoint"),

    // 2xx Success

    /**
     * 请求成功
     * {@code 200 OK}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.1">HTTP/1.1: Semantics and Content, section 6.3.1</a>
     */
    OK(200, Series.SUCCESSFUL, "OK"),

    /**
     * 请求成功并且服务器创建了一个新的资源。通常用于 POST 请求后返回新创建资源的位置。
     * {@code 201 Created}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.2">HTTP/1.1: Semantics and Content, section 6.3.2</a>
     */
    CREATED(201, Series.SUCCESSFUL, "Created"),

    /**
     * 请求已被接受,但还未处理完成。异步处理的结果可能稍后可用。
     * {@code 202 Accepted}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.3">HTTP/1.1: Semantics and Content, section 6.3.3</a>
     */
    ACCEPTED(202, Series.SUCCESSFUL, "Accepted"),

    /**
     * 返回的信息不是由原始服务器提供的,而是来自缓存或第三方副本
     * {@code 203 Non-Authoritative Information}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.4">HTTP/1.1: Semantics and Content, section 6.3.4</a>
     */
    NON_AUTHORITATIVE_INFORMATION(203, Series.SUCCESSFUL, "Non-Authoritative Information"),

    /**
     * 请求成功,但没有内容返回。通常用于 PUT 或 DELETE 请求。
     * {@code 204 No Content}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.5">HTTP/1.1: Semantics and Content, section 6.3.5</a>
     */
    NO_CONTENT(204, Series.SUCCESSFUL, "No Content"),

    /**
     * 告诉客户端重置文档视图(例如清除表单)。不常用。
     * {@code 205 Reset Content}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.3.6">HTTP/1.1: Semantics and Content, section 6.3.6</a>
     */
    RESET_CONTENT(205, Series.SUCCESSFUL, "Reset Content"),
    /**
     * 部分内容响应。通常与范围请求一起使用。
     * {@code 206 Partial Content}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.1">HTTP/1.1: Range Requests, section 4.1</a>
     */
    PARTIAL_CONTENT(206, Series.SUCCESSFUL, "Partial Content"),

    /**
     * WebDAV 请求可能包含多个操作状态
     * {@code 207 Multi-Status}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc4918#section-13">WebDAV</a>
     */
    MULTI_STATUS(207, Series.SUCCESSFUL, "Multi-Status"),

    /**
     * 用于避免重复列举成员节点。
     * {@code 208 Already Reported}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc5842#section-7.1">WebDAV Binding Extensions</a>
     */
    ALREADY_REPORTED(208, Series.SUCCESSFUL, "Already Reported"),

    /**
     * 服务器已经完成了对资源的部分 GET 请求,并且请求的部分已经在实体头中传输。
     * {@code 226 IM Used}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc3229#section-10.4.1">Delta encoding in HTTP</a>
     */
    IM_USED(226, Series.SUCCESSFUL, "IM Used"),

    // 3xx Redirection

    /**
     * 请求的资源对应多个位置,客户端可以选择其中一个。
     * {@code 300 Multiple Choices}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.1">HTTP/1.1: Semantics and Content, section 6.4.1</a>
     */
    MULTIPLE_CHOICES(300, Series.REDIRECTION, "Multiple Choices"),

    /**
     * 请求的资源已永久移动到新位置,客户端应该更新书签等。
     * {@code 301 Moved Permanently}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.2">HTTP/1.1: Semantics and Content, section 6.4.2</a>
     */
    MOVED_PERMANENTLY(301, Series.REDIRECTION, "Moved Permanently"),

    /**
     * 请求的资源暂时从不同的 URI 响应请求。由于历史原因,很多浏览器会错误地将此视为临时重定向。
     * {@code 302 Found}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.3">HTTP/1.1: Semantics and Content, section 6.4.3</a>
     */
    FOUND(302, Series.REDIRECTION, "Found"),

    /**
     * 请求的资源暂时从不同的 URI 响应请求。由于历史原因,很多浏览器会错误地将此视为临时重定向。
     * {@code 302 Moved Temporarily}.
     * @see <a href="https://tools.ietf.org/html/rfc1945#section-9.3">HTTP/1.0, section 9.3</a>
     * @deprecated in favor of {@link #FOUND} which will be returned from {@code HttpStatus.valueOf(302)}
     */
    //@Deprecated
    //MOVED_TEMPORARILY(302, Series.REDIRECTION, "Moved Temporarily"),

    /**
     * 建议客户端访问另一个 URL 来获取所请求的资源。
     * {@code 303 See Other}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.4">HTTP/1.1: Semantics and Content, section 6.4.4</a>
     */
    SEE_OTHER(303, Series.REDIRECTION, "See Other"),

    /**
     * 资源未修改。通常用于条件 GET 请求,服务器告诉客户端资源自上次请求以来没有变化。
     * {@code 304 Not Modified}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-4.1">HTTP/1.1: Conditional Requests, section 4.1</a>
     */
    NOT_MODIFIED(304, Series.REDIRECTION, "Not Modified"),

    /**
     * 请求的资源必须通过代理访问。很少使用。
     * {@code 305 Use Proxy}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.5">HTTP/1.1: Semantics and Content, section 6.4.5</a>
     * @deprecated due to security concerns regarding in-band configuration of a proxy
     */
    @Deprecated
    USE_PROXY(305, Series.REDIRECTION, "Use Proxy"),

    /**
     * 请求的资源临时从不同的 URI 响应请求。除了不允许改变请求方法外,其他行为类似于 302。
     * {@code 307 Temporary Redirect}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.4.7">HTTP/1.1: Semantics and Content, section 6.4.7</a>
     */
    TEMPORARY_REDIRECT(307, Series.REDIRECTION, "Temporary Redirect"),

    /**
     * 求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。
     * {@code 308 Permanent Redirect}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7238">RFC 7238</a>
     */
    PERMANENT_REDIRECT(308, Series.REDIRECTION, "Permanent Redirect"),

    // --- 4xx Client Error ---

    /**
     * 请求无效或格式错误
     * {@code 400 Bad Request}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.1">HTTP/1.1: Semantics and Content, section 6.5.1</a>
     */
    BAD_REQUEST(400, Series.CLIENT_ERROR, "Bad Request"),

    /**
     * 请求要求用户身份验证。如果身份验证失败或未提供,则返回此状态码。
     * {@code 401 Unauthorized}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-3.1">HTTP/1.1: Authentication, section 3.1</a>
     */
    UNAUTHORIZED(401, Series.CLIENT_ERROR, "Unauthorized"),

    /**
     * 保留用于未来使用。目前很少使用。
     * {@code 402 Payment Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.2">HTTP/1.1: Semantics and Content, section 6.5.2</a>
     */
    PAYMENT_REQUIRED(402, Series.CLIENT_ERROR, "Payment Required"),

    /**
     * 服务器理解请求,但是拒绝执行它。权限不足时常见。
     * {@code 403 Forbidden}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.3">HTTP/1.1: Semantics and Content, section 6.5.3</a>
     */
    FORBIDDEN(403, Series.CLIENT_ERROR, "Forbidden"),

    /**
     * 请求的资源不存在。
     * {@code 404 Not Found}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.4">HTTP/1.1: Semantics and Content, section 6.5.4</a>
     */
    NOT_FOUND(404, Series.CLIENT_ERROR, "Not Found"),

    /**
     * 请求的方法对于目标资源是不允许的。
     * {@code 405 Method Not Allowed}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.5">HTTP/1.1: Semantics and Content, section 6.5.5</a>
     */
    METHOD_NOT_ALLOWED(405, Series.CLIENT_ERROR, "Method Not Allowed"),

    /**
     * 服务器无法根据客户端请求的内容特性生成相应的内容。
     * {@code 406 Not Acceptable}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.6">HTTP/1.1: Semantics and Content, section 6.5.6</a>
     */
    NOT_ACCEPTABLE(406, Series.CLIENT_ERROR, "Not Acceptable"),

    /**
     * 类似于 401,但是指代理的身份验证失败。
     * {@code 407 Proxy Authentication Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-3.2">HTTP/1.1: Authentication, section 3.2</a>
     */
    PROXY_AUTHENTICATION_REQUIRED(407, Series.CLIENT_ERROR, "Proxy Authentication Required"),

    /**
     * 请求超时。
     * {@code 408 Request Timeout}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.7">HTTP/1.1: Semantics and Content, section 6.5.7</a>
     */
    REQUEST_TIMEOUT(408, Series.CLIENT_ERROR, "Request Timeout"),

    /**
     * 请求冲突,比如尝试创建已有资源。
     * {@code 409 Conflict}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.8">HTTP/1.1: Semantics and Content, section 6.5.8</a>
     */
    CONFLICT(409, Series.CLIENT_ERROR, "Conflict"),

    /**
     * 请求的资源在服务器上不再可用,而且没有任何已知的转发地址。
     * {@code 410 Gone}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.9">
     * HTTP/1.1: Semantics and Content, section 6.5.9</a>
     */
    GONE(410, Series.CLIENT_ERROR, "Gone"),

    /**
     * 服务器拒绝处理缺少 Content-Length 头部字段的请求。
     * {@code 411 Length Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.10">
     * HTTP/1.1: Semantics and Content, section 6.5.10</a>
     */
    LENGTH_REQUIRED(411, Series.CLIENT_ERROR, "Length Required"),

    /**
     * 服务器在验证请求中给出的前提条件时,发现该前提条件为假。
     * {@code 412 Precondition failed}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-4.2">
     * HTTP/1.1: Conditional Requests, section 4.2</a>
     */
    PRECONDITION_FAILED(412, Series.CLIENT_ERROR, "Precondition Failed"),

    /**
     * 请求实体太大,服务器拒绝处理。
     * {@code 413 Payload Too Large}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.11">
     * HTTP/1.1: Semantics and Content, section 6.5.11</a>
     * @since 4.1
     */
    PAYLOAD_TOO_LARGE(413, Series.CLIENT_ERROR, "Payload Too Large"),

    /**
     *
     * {@code 413 Request Entity Too Large}.
     * @see <a href="https://tools.ietf.org/html/rfc2616#section-10.4.14">HTTP/1.1, section 10.4.14</a>
     * @deprecated in favor of {@link #PAYLOAD_TOO_LARGE} which will be
     * returned from {@code HttpStatus.valueOf(413)}
     */
    //@Deprecated
    //REQUEST_ENTITY_TOO_LARGE(413, Series.CLIENT_ERROR, "Request Entity Too Large"),

    /**
     * 请求 URI 太长,服务器拒绝处理。
     * {@code 414 URI Too Long}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.12">
     * HTTP/1.1: Semantics and Content, section 6.5.12</a>
     * @since 4.1
     */
    URI_TOO_LONG(414, Series.CLIENT_ERROR, "URI Too Long"),

    /**
     * 请求 URI 太长,服务器拒绝处理。
     * {@code 414 Request-URI Too Long}.
     * @see <a href="https://tools.ietf.org/html/rfc2616#section-10.4.15">HTTP/1.1, section 10.4.15</a>
     * @deprecated in favor of {@link #URI_TOO_LONG} which will be returned from {@code HttpStatus.valueOf(414)}
     */
    //@Deprecated
    //REQUEST_URI_TOO_LONG(414, Series.CLIENT_ERROR, "Request-URI Too Long"),

    /**
     * 对于请求中指定的实体,服务器不支持该媒体类型。
     * {@code 415 Unsupported Media Type}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.13">
     * HTTP/1.1: Semantics and Content, section 6.5.13</a>
     */
    UNSUPPORTED_MEDIA_TYPE(415, Series.CLIENT_ERROR, "Unsupported Media Type"),

    /**
     * 客户端请求的范围无效。
     * {@code 416 Requested Range Not Satisfiable}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.4">HTTP/1.1: Range Requests, section 4.4</a>
     */
    REQUESTED_RANGE_NOT_SATISFIABLE(416, Series.CLIENT_ERROR, "Requested range not satisfiable"),

    /**
     * 服务器不能满足 Expect 请求头字段的要求。
     * {@code 417 Expectation Failed}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.5.14">
     * HTTP/1.1: Semantics and Content, section 6.5.14</a>
     */
    EXPECTATION_FAILED(417, Series.CLIENT_ERROR, "Expectation Failed"),

    /**
     * {@code 418 I'm a teapot}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2324#section-2.3.2">HTCPCP/1.0</a>
     */
    I_AM_A_TEAPOT(418, Series.CLIENT_ERROR, "I'm a teapot"),

    /**
     * @deprecated See
     * <a href="https://tools.ietf.org/rfcdiff?difftype=--hwdiff&amp;url2=draft-ietf-webdav-protocol-06.txt">
     * WebDAV Draft Changes</a>
     */
    @Deprecated
    INSUFFICIENT_SPACE_ON_RESOURCE(419, Series.CLIENT_ERROR, "Insufficient Space On Resource"),
    /**
     * @deprecated See
     * <a href="https://tools.ietf.org/rfcdiff?difftype=--hwdiff&amp;url2=draft-ietf-webdav-protocol-06.txt">
     * WebDAV Draft Changes</a>
     */
    @Deprecated
    METHOD_FAILURE(420, Series.CLIENT_ERROR, "Method Failure"),

    /**
     * 请求被错误地发给了无法产生响应的服务器。
     *
     * @deprecated See <a href="https://tools.ietf.org/rfcdiff?difftype=--hwdiff&amp;url2=draft-ietf-webdav-protocol-06.txt">
     * WebDAV Draft Changes</a>
     */
    @Deprecated
    DESTINATION_LOCKED(421, Series.CLIENT_ERROR, "Destination Locked"),


    /**
     * 请求格式正确,但由于语义错误而无法处理。
     * {@code 422 Unprocessable Entity}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc4918#section-11.2">WebDAV</a>
     */
    UNPROCESSABLE_ENTITY(422, Series.CLIENT_ERROR, "Unprocessable Entity"),

    /**
     * 当前资源被锁定。
     * {@code 423 Locked}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc4918#section-11.3">WebDAV</a>
     */
    LOCKED(423, Series.CLIENT_ERROR, "Locked"),

    /**
     * 请求失败依赖于另一个操作,而那个操作失败了。
     * {@code 424 Failed Dependency}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc4918#section-11.4">WebDAV</a>
     */
    FAILED_DEPENDENCY(424, Series.CLIENT_ERROR, "Failed Dependency"),

    /**
     * 服务器不愿意风险处理可能会被重播的请求。
     * {@code 425 Too Early}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc8470">RFC 8470</a>
     * @since 5.2
     */
    TOO_EARLY(425, Series.CLIENT_ERROR, "Too Early"),

    /**
     * 客户端应当切换协议。服务器关闭连接以强制客户端切换。
     * {@code 426 Upgrade Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2817#section-6">Upgrading to TLS Within HTTP/1.1</a>
     */
    UPGRADE_REQUIRED(426, Series.CLIENT_ERROR, "Upgrade Required"),

    /**
     * 服务器要求请求附带前提条件。
     * {@code 428 Precondition Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6585#section-3">Additional HTTP Status Codes</a>
     */
    PRECONDITION_REQUIRED(428, Series.CLIENT_ERROR, "Precondition Required"),

    /**
     * 用户在给定时间内发送了太多的请求(限流)。
     * {@code 429 Too Many Requests}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6585#section-4">Additional HTTP Status Codes</a>
     */
    TOO_MANY_REQUESTS(429, Series.CLIENT_ERROR, "Too Many Requests"),

    /**
     * 服务器拒绝处理,因为请求头字段太大。
     * {@code 431 Request Header Fields Too Large}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6585#section-5">Additional HTTP Status Codes</a>
     */
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, Series.CLIENT_ERROR, "Request Header Fields Too Large"),

    /**
     * 请求非法,通常是由于法律原因。
     * {@code 451 Unavailable For Legal Reasons}.
     *
     * @see <a href="https://tools.ietf.org/html/draft-ietf-httpbis-legally-restricted-status-04">
     * An HTTP Status Code to Report Legal Obstacles</a>
     * @since 4.3
     */
    UNAVAILABLE_FOR_LEGAL_REASONS(451, Series.CLIENT_ERROR, "Unavailable For Legal Reasons"),

    // --- 5xx Server Error ---

    /**
     * 服务器遇到了意外情况,阻止了其完成请求。
     * {@code 500 Internal Server Error}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.1">HTTP/1.1: Semantics and Content, section 6.6.1</a>
     */
    INTERNAL_SERVER_ERROR(500, Series.SERVER_ERROR, "Internal Server Error"),

    /**
     * 服务器不支持实现请求所需的功能。
     * {@code 501 Not Implemented}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.2">HTTP/1.1: Semantics and Content, section 6.6.2</a>
     */
    NOT_IMPLEMENTED(501, Series.SERVER_ERROR, "Not Implemented"),

    /**
     * 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效响应。
     * {@code 502 Bad Gateway}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.3">HTTP/1.1: Semantics and Content, section 6.6.3</a>
     */
    BAD_GATEWAY(502, Series.SERVER_ERROR, "Bad Gateway"),

    /**
     * 服务器当前无法处理请求。这可能是由于过载或维护。
     * {@code 503 Service Unavailable}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.4">HTTP/1.1: Semantics and Content, section 6.6.4</a>
     */
    SERVICE_UNAVAILABLE(503, Series.SERVER_ERROR, "Service Unavailable"),

    /**
     * 作为网关或代理工作的服务器未能及时从上游服务器获得响应。
     * {@code 504 Gateway Timeout}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.5">HTTP/1.1: Semantics and Content, section 6.6.5</a>
     */
    GATEWAY_TIMEOUT(504, Series.SERVER_ERROR, "Gateway Timeout"),

    /**
     * 服务器不支持请求中使用的 HTTP 版本。
     * {@code 505 HTTP Version Not Supported}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-6.6.6">HTTP/1.1: Semantics and Content, section 6.6.6</a>
     */
    HTTP_VERSION_NOT_SUPPORTED(505, Series.SERVER_ERROR, "HTTP Version not supported"),

    /**
     * 服务器有一个内部配置错误:所选的变体资源本身可以是协商过程的一部分。
     * {@code 506 Variant Also Negotiates}
     *
     * @see <a href="https://tools.ietf.org/html/rfc2295#section-8.1">Transparent Content Negotiation</a>
     */
    VARIANT_ALSO_NEGOTIATES(506, Series.SERVER_ERROR, "Variant Also Negotiates"),

    /**
     * 服务器无法存储完成请求所需要的内容。WebDAV 使用。
     * {@code 507 Insufficient Storage}
     *
     * @see <a href="https://tools.ietf.org/html/rfc4918#section-11.5">WebDAV</a>
     */
    INSUFFICIENT_STORAGE(507, Series.SERVER_ERROR, "Insufficient Storage"),

    /**
     * 服务器检测到了无限循环。
     * {@code 508 Loop Detected}
     *
     * @see <a href="https://tools.ietf.org/html/rfc5842#section-7.2">WebDAV Binding Extensions</a>
     */
    LOOP_DETECTED(508, Series.SERVER_ERROR, "Loop Detected"),

    /**
     * {@code 509 Bandwidth Limit Exceeded}
     */
    BANDWIDTH_LIMIT_EXCEEDED(509, Series.SERVER_ERROR, "Bandwidth Limit Exceeded"),

    /**
     * 请求需要进一步扩展才能完成。
     * {@code 510 Not Extended}
     *
     * @see <a href="https://tools.ietf.org/html/rfc2774#section-7">HTTP Extension Framework</a>
     */
    NOT_EXTENDED(510, Series.SERVER_ERROR, "Not Extended"),
    /**
     * 客户端需要进行身份验证以获得网络访问权限
     * {@code 511 Network Authentication Required}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6585#section-6">Additional HTTP Status Codes</a>
     */
    NETWORK_AUTHENTICATION_REQUIRED(511, Series.SERVER_ERROR, "Network Authentication Required");


    private static final HttpClientStatus[] VALUES;

    static {
        VALUES = values();
    }


    private final int value;

    private final Series series;

    private final String reasonPhrase;

    HttpClientStatus(int value, Series series, String reasonPhrase) {
        this.value = value;
        this.series = series;
        this.reasonPhrase = reasonPhrase;
    }


    /**
     * Return the integer value of this status code.
     */
    public int value() {
        return this.value;
    }

    /**
     * Return the HTTP status series of this status code.
     *
     * @see Series
     */
    public Series series() {
        return this.series;
    }

    /**
     * Return the reason phrase of this status code.
     */
    public String getReasonPhrase() {
        return this.reasonPhrase;
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #series()
     * @since 4.0
     */
    public boolean is1xxInformational() {
        return (series() == Series.INFORMATIONAL);
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #series()
     * @since 4.0
     */
    public boolean is2xxSuccessful() {
        return (series() == Series.SUCCESSFUL);
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #series()
     * @since 4.0
     */
    public boolean is3xxRedirection() {
        return (series() == Series.REDIRECTION);
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #series()
     * @since 4.0
     */
    public boolean is4xxClientError() {
        return (series() == Series.CLIENT_ERROR);
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #series()
     * @since 4.0
     */
    public boolean is5xxServerError() {
        return (series() == Series.SERVER_ERROR);
    }

    /**
     * Whether this status code is in the HTTP series
     * <p>This is a shortcut for checking the value of {@link #series()}.
     *
     * @see #is4xxClientError()
     * @see #is5xxServerError()
     * @since 5.0
     */
    public boolean isError() {
        return (is4xxClientError() || is5xxServerError());
    }

    /**
     * Return a string representation of this status code.
     */
    @Override
    public String toString() {
        return this.value + " " + name();
    }


    /**
     * Return the {@code HttpStatus} enum constant with the specified numeric value.
     *
     * @param statusCode the numeric value of the enum to be returned
     * @return the enum constant with the specified numeric value
     * @throws IllegalArgumentException if this enum has no constant for the specified numeric value
     */
    public static HttpClientStatus valueOf(int statusCode) {
        HttpClientStatus status = resolve(statusCode);
        if (status == null) {
            throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
        }
        return status;
    }

    /**
     * Resolve the given status code to an {@code HttpStatus}, if possible.
     *
     * @param statusCode the HTTP status code (potentially non-standard)
     * @return the corresponding {@code HttpStatus}, or {@code null} if not found
     * @since 5.0
     */
    @Nullable
    public static HttpClientStatus resolve(int statusCode) {
        // Use cached VALUES instead of values() to prevent array allocation.
        for (HttpClientStatus status : VALUES) {
            if (status.value == statusCode) {
                return status;
            }
        }
        return null;
    }

    /**
     * Enumeration of HTTP status series.
     * <p>Retrievable via {@link HttpStatus#series()}.
     */
    public enum Series {

        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);

        private final int value;

        Series(int value) {
            this.value = value;
        }

        /**
         * Return the integer value of this status series. Ranges from 1 to 5.
         */
        public int value() {
            return this.value;
        }

        /**
         * Return the {@code Series} enum constant for the supplied {@code HttpStatus}.
         *
         * @param status a standard HTTP status enum constant
         * @return the {@code Series} enum constant for the supplied {@code HttpStatus}
         * @deprecated as of 5.3, in favor of invoking {@link HttpStatus#series()} directly
         */
        @Deprecated
        public static Series valueOf(HttpClientStatus status) {
            return status.series;
        }

        /**
         * Return the {@code Series} enum constant for the supplied status code.
         *
         * @param statusCode the HTTP status code (potentially non-standard)
         * @return the {@code Series} enum constant for the supplied status code
         * @throws IllegalArgumentException if this enum has no corresponding constant
         */
        public static Series valueOf(int statusCode) {
            Series series = resolve(statusCode);
            if (series == null) {
                throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
            }
            return series;
        }

        /**
         * Resolve the given status code to an {@code Series}, if possible.
         *
         * @param statusCode the HTTP status code (potentially non-standard)
         * @return the corresponding {@code Series}, or {@code null} if not found
         * @since 5.1.3
         */
        @Nullable
        public static Series resolve(int statusCode) {
            int seriesCode = statusCode / 100;
            for (Series series : values()) {
                if (series.value == seriesCode) {
                    return series;
                }
            }
            return null;
        }
    }

}

  • 封装HttpClientHeaders接口,避免框架替换的时候需要整改整个项目
package com.zzc.component.http;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class HttpClientHeaders implements Map<String, String>, Serializable {

    /**
     * The HTTP {@code Accept} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
     */
    public static final String ACCEPT = "Accept";
    /**
     * The HTTP {@code Accept-Charset} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
     */
    public static final String ACCEPT_CHARSET = "Accept-Charset";
    /**
     * The HTTP {@code Accept-Encoding} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
     */
    public static final String ACCEPT_ENCODING = "Accept-Encoding";
    /**
     * The HTTP {@code Accept-Language} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
     */
    public static final String ACCEPT_LANGUAGE = "Accept-Language";
    /**
     * The HTTP {@code Accept-Patch} header field name.
     * @since 5.3.6
     * @see <a href="https://tools.ietf.org/html/rfc5789#section-3.1">Section 3.1 of RFC 5789</a>
     */
    public static final String ACCEPT_PATCH = "Accept-Patch";
    /**
     * The HTTP {@code Accept-Ranges} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
     */
    public static final String ACCEPT_RANGES = "Accept-Ranges";
    /**
     * The CORS {@code Access-Control-Allow-Credentials} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
    /**
     * The CORS {@code Access-Control-Allow-Headers} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    /**
     * The CORS {@code Access-Control-Allow-Methods} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    /**
     * The CORS {@code Access-Control-Allow-Origin} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    /**
     * The CORS {@code Access-Control-Expose-Headers} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
    /**
     * The CORS {@code Access-Control-Max-Age} response header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
    /**
     * The CORS {@code Access-Control-Request-Headers} request header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    /**
     * The CORS {@code Access-Control-Request-Method} request header field name.
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
    /**
     * The HTTP {@code Age} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
     */
    public static final String AGE = "Age";
    /**
     * The HTTP {@code Allow} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
     */
    public static final String ALLOW = "Allow";
    /**
     * The HTTP {@code Authorization} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
     */
    public static final String AUTHORIZATION = "Authorization";
    /**
     * The HTTP {@code Cache-Control} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
     */
    public static final String CACHE_CONTROL = "Cache-Control";
    /**
     * The HTTP {@code Connection} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
     */
    public static final String CONNECTION = "Connection";
    /**
     * The HTTP {@code Content-Encoding} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
     */
    public static final String CONTENT_ENCODING = "Content-Encoding";
    /**
     * The HTTP {@code Content-Disposition} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a>
     */
    public static final String CONTENT_DISPOSITION = "Content-Disposition";
    /**
     * The HTTP {@code Content-Language} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
     */
    public static final String CONTENT_LANGUAGE = "Content-Language";
    /**
     * The HTTP {@code Content-Length} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
     */
    public static final String CONTENT_LENGTH = "Content-Length";
    /**
     * The HTTP {@code Content-Location} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
     */
    public static final String CONTENT_LOCATION = "Content-Location";
    /**
     * The HTTP {@code Content-Range} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
     */
    public static final String CONTENT_RANGE = "Content-Range";
    /**
     * The HTTP {@code Content-Type} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
     */
    public static final String CONTENT_TYPE = "Content-Type";
    /**
     * The HTTP {@code Cookie} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
     */
    public static final String COOKIE = "Cookie";
    /**
     * The HTTP {@code Date} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
     */
    public static final String DATE = "Date";
    /**
     * The HTTP {@code ETag} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
     */
    public static final String ETAG = "ETag";
    /**
     * The HTTP {@code Expect} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
     */
    public static final String EXPECT = "Expect";
    /**
     * The HTTP {@code Expires} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
     */
    public static final String EXPIRES = "Expires";
    /**
     * The HTTP {@code From} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
     */
    public static final String FROM = "From";
    /**
     * The HTTP {@code Host} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
     */
    public static final String HOST = "Host";
    /**
     * The HTTP {@code If-Match} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
     */
    public static final String IF_MATCH = "If-Match";
    /**
     * The HTTP {@code If-Modified-Since} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
     */
    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    /**
     * The HTTP {@code If-None-Match} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
     */
    public static final String IF_NONE_MATCH = "If-None-Match";
    /**
     * The HTTP {@code If-Range} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
     */
    public static final String IF_RANGE = "If-Range";
    /**
     * The HTTP {@code If-Unmodified-Since} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
     */
    public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
    /**
     * The HTTP {@code Last-Modified} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
     */
    public static final String LAST_MODIFIED = "Last-Modified";
    /**
     * The HTTP {@code Link} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc5988">RFC 5988</a>
     */
    public static final String LINK = "Link";
    /**
     * The HTTP {@code Location} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
     */
    public static final String LOCATION = "Location";
    /**
     * The HTTP {@code Max-Forwards} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
     */
    public static final String MAX_FORWARDS = "Max-Forwards";
    /**
     * The HTTP {@code Origin} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
     */
    public static final String ORIGIN = "Origin";
    /**
     * The HTTP {@code Pragma} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
     */
    public static final String PRAGMA = "Pragma";
    /**
     * The HTTP {@code Proxy-Authenticate} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
     */
    public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
    /**
     * The HTTP {@code Proxy-Authorization} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
     */
    public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
    /**
     * The HTTP {@code Range} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
     */
    public static final String RANGE = "Range";
    /**
     * The HTTP {@code Referer} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
     */
    public static final String REFERER = "Referer";
    /**
     * The HTTP {@code Retry-After} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
     */
    public static final String RETRY_AFTER = "Retry-After";
    /**
     * The HTTP {@code Server} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
     */
    public static final String SERVER = "Server";
    /**
     * The HTTP {@code Set-Cookie} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
     */
    public static final String SET_COOKIE = "Set-Cookie";
    /**
     * The HTTP {@code Set-Cookie2} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a>
     */
    public static final String SET_COOKIE2 = "Set-Cookie2";
    /**
     * The HTTP {@code TE} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
     */
    public static final String TE = "TE";
    /**
     * The HTTP {@code Trailer} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
     */
    public static final String TRAILER = "Trailer";
    /**
     * The HTTP {@code Transfer-Encoding} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
     */
    public static final String TRANSFER_ENCODING = "Transfer-Encoding";
    /**
     * The HTTP {@code Upgrade} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
     */
    public static final String UPGRADE = "Upgrade";
    /**
     * The HTTP {@code User-Agent} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
     */
    public static final String USER_AGENT = "User-Agent";
    /**
     * The HTTP {@code Vary} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
     */
    public static final String VARY = "Vary";
    /**
     * The HTTP {@code Via} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
     */
    public static final String VIA = "Via";
    /**
     * The HTTP {@code Warning} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
     */
    public static final String WARNING = "Warning";
    /**
     * The HTTP {@code WWW-Authenticate} header field name.
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
     */
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";

    private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");


    final Map<String, String> headers;

    public HttpClientHeaders() {
        this(new HashMap<>());
    }

    public HttpClientHeaders(Map<String, String> headers) {
        this.headers = headers;
    }

    @Override
    public int size() {
        return this.headers.size();
    }

    @Override
    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    @Override
    public String get(Object key) {
        return this.headers.get(key);
    }

    @Override
    public String put(String key, String value) {
        return this.headers.put(key, value);
    }

    @Override
    public String remove(Object key) {
        return this.headers.remove(key);
    }

    @Override
    public void putAll(Map<? extends String, ? extends String> m) {
        this.headers.putAll(m);
    }

    @Override
    public void clear() {
        this.headers.clear();
    }

    @Override
    public Set<String> keySet() {
        return new HashSet<>(this.headers.keySet());
    }

    @Override
    public Collection<String> values() {
        return new HashSet<>(this.headers.values());
    }

    @Override
    public Set<Entry<String, String>> entrySet() {
        return new HashSet<>(this.headers.entrySet());
    }

    public static HttpClientHeadersBuilder builder() {
        return new HttpClientHeadersBuilder();
    }

    public static class HttpClientHeadersBuilder {
        private final Map<String, String> headers;

        public HttpClientHeadersBuilder() {
            this(new HashMap<>());
        }

        public HttpClientHeadersBuilder(Map<String, String> headers) {
            this.headers = headers;
        }

        public HttpClientHeadersBuilder addHeaders(Map<String, String> headers) {
            this.headers.putAll(headers);
            return this;
        }

        public HttpClientHeadersBuilder addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public HttpClientHeaders build() {
            return new HttpClientHeaders(this.headers);
        }
    }

}

  • 封装响应的消息接口
package com.zzc.component.http;

import java.io.IOException;
import java.io.InputStream;

public interface HttpClientResponse extends AutoCloseable {

    InputStream getBody() throws IOException;

    HttpClientStatus getStatusCode() throws IOException;

    int getRawStatusCode() throws IOException;

    String getStatusText() throws IOException;

    @Override
    void close();

}

  • 封装HttpClientResponseExtractor作为处理InputStream等操作的接口
package com.zzc.component.http;

import java.io.IOException;

public interface HttpClientResponseExtractor<T> {

    T extractData(HttpClientResponse response) throws IOException;

}

  • 封装HttpClientResponseEntity作为接收消息体
package com.zzc.component.http;

public class HttpClientResponseEntity<T> {

    private final T body;

    private final Object status;

    private final HttpClientHeaders headers;

    public HttpClientResponseEntity(T body) {
        this(body, null, null);
    }

    public HttpClientResponseEntity(T body, HttpClientHeaders headers, Object status) {
        this.body = body;
        this.status = status;
        this.headers = headers;
    }

    public HttpClientHeaders getHeaders() {
        return this.headers;
    }

    public T getBody() {
        return this.body;
    }

    public Object getStatus() {
        return this.status;
    }

    public HttpClientStatus getStatusCode() {
        if (status instanceof HttpClientStatus) {
            return (HttpClientStatus) this.status;
        } else if (status instanceof Integer) {
            return HttpClientStatus.valueOf((Integer) this.status);
        } else {
            System.out.println(status.getClass());
            return HttpClientStatus.INTERNAL_SERVER_ERROR;
        }
    }

    public int getStatusCodeValue() {
        if (this.status instanceof HttpClientStatus) {
            return ((HttpClientStatus) this.status).value();
        } else if (this.status instanceof Integer) {
            return ((Integer) this.status);
        } else {
            return HttpClientStatus.INTERNAL_SERVER_ERROR.value();
        }
    }


}

  • 创建泛型保存抽象类,用于解决多重泛型对象序列问题(内部先序列化为String.class做法性能一般)
package com.zzc.component.http;

import com.alibaba.fastjson2.TypeReference;
public abstract class Type<T> extends TypeReference<T> {
}

  • 通过SpringBoot的方式进行配置
package com.zzc.component.http.httpclient;

import com.zzc.component.http.HttpClientProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@Configuration
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientFactory {

    /**
     * 指定域名地址的请求配置
     */
    private static Map<String, RequestConfig> TARGET_HOST_REQUEST_CONFIG = new HashMap<>();

    /**
     * 指定域名地址的请求头
     */
    private static Map<String, Map<String, String>> TARGET_HOST_HEADERS = new HashMap<>();

    private final HttpClientProperties httpClientProperties;

    public HttpClientFactory(HttpClientProperties httpClientProperties) {
        this.httpClientProperties = httpClientProperties;
    }

    protected HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();

        factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
        factory.setReadTimeout(httpClientProperties.getReadTimeout());
        factory.setConnectionRequestTimeout(httpClientProperties.getRequestTimeout());
        factory.setHttpClient(httpClient());
        return factory;
    }

    public HttpClient httpClient() {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        try {
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", SSLConnectionSocketFactory.getSocketFactory())
                    .build();

            PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
            //设置全局最大连接数
            httpClientConnectionManager.setMaxTotal(httpClientProperties.getConnMaxTotal());
            //设置全局默认最大并发数
            httpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getMaxPerRoute());
            //设置指定域名的最大并发数
            setTargetHostMaxPerRoute(httpClientConnectionManager);
            //设置链接管理器
            httpClientBuilder.setConnectionManager(httpClientConnectionManager);
            //设置默认请求handler
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientProperties.getRetryNum(), httpClientProperties.getRetryNum() != 0));
            //设置全局默认请求头
            List<Header> headers = genHeaders();
            if (!headers.isEmpty()) {
                httpClientBuilder.setDefaultHeaders(headers);
            }

            //设置指定地址的Keep-Alive策略
            httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());

            //添加请求拦截器,默认请求头,请求超时设置等
            httpClientBuilder.addInterceptorFirst(httpRequestInterceptor());

            //设置定时关闭无效链接
            httpClientBuilder.evictIdleConnections(30L, TimeUnit.SECONDS);
            return httpClientBuilder.build();
        } catch (Exception e) {
            log.error("init http factory error", e);
        }
        return null;
    }

    private void setTargetHostMaxPerRoute(PoolingHttpClientConnectionManager httpClientConnectionManager) {
        List<HttpClientProperties.TargetHostConfig> targetHosts = httpClientProperties.getTargetHosts();
        if (targetHosts != null && !targetHosts.isEmpty()) {
            for (HttpClientProperties.TargetHostConfig config : targetHosts) {
                if (config.getHostname() == null || config.getMaxPerRoute() == null) {//地址没有配置的则跳过
                    continue;
                }
                try {
                    HttpHost httpHost = HttpHost.create(config.getHostname());
                    httpClientConnectionManager.setMaxPerRoute(new HttpRoute(httpHost), config.getMaxPerRoute());
                } catch (Exception e) {
                    log.error("setMaxPerRoute error. hostname:{}", config.getHostname(), e);
                }
            }
        }
    }

    private HttpRequestInterceptor httpRequestInterceptor() {
        initialize();
        //添加请求拦截器,默认请求头,请求超时设置等
        return (httpRequest, httpContext) -> {
            HttpHost httpHost = (HttpHost) httpContext.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            String host = httpHost.toHostString();
            String hostName = httpHost.getHostName();
            int port = httpHost.getPort();
            log.debug("Interceptors hostName:{}, port:{}, host:{}", hostName, port, host);
            if (TARGET_HOST_REQUEST_CONFIG.containsKey(host)) {
                RequestConfig requestConfig = TARGET_HOST_REQUEST_CONFIG.get(host);
                httpContext.setAttribute(HttpClientContext.REQUEST_CONFIG, requestConfig);
            }
            if (TARGET_HOST_HEADERS.containsKey(host)) {
                TARGET_HOST_HEADERS.get(host).forEach(httpRequest::addHeader);
            }
        };
    }

    /**
     * 初始化相关配置
     */
    private void initialize() {
        List<HttpClientProperties.TargetHostConfig> targetHosts = httpClientProperties.getTargetHosts();
        if (targetHosts != null && !targetHosts.isEmpty()) {
            for (HttpClientProperties.TargetHostConfig config : targetHosts) {
                if (config.getHostname() == null) {//地址没有配置的则跳过
                    continue;
                }
                log.info("addInterceptors config host:{}", config.getHostname());
                //超时时间等配置,如果没有配置则使用全局配置
                Integer connectTimeout = config.getConnectTimeout() == null ? httpClientProperties.getConnectTimeout() : config.getConnectTimeout();
                Integer readTimeout = config.getReadTimeout() == null ? httpClientProperties.getReadTimeout() : config.getReadTimeout();
                Integer requestTimeout = config.getRequestTimeout() == null ? httpClientProperties.getRequestTimeout() : config.getRequestTimeout();

                RequestConfig requestConfig = RequestConfig.custom()
                        .setConnectTimeout(connectTimeout == null ? -1 : connectTimeout)
                        .setSocketTimeout(readTimeout == null ? -1 : readTimeout)
                        .setConnectionRequestTimeout(requestTimeout == null ? -1 : requestTimeout)
                        .build();

                TARGET_HOST_REQUEST_CONFIG.put(config.getHostname(), requestConfig);

                if (config.getHeaders() != null && !config.getHeaders().isEmpty()) {
                    TARGET_HOST_HEADERS.computeIfAbsent(config.getHostname(), k -> new HashMap<>());
                    config.getHeaders().forEach((key, value) -> TARGET_HOST_HEADERS.get(config.getHostname()).put(key, value));
                }
                if (config.getKeepAliveTime() != null) {
                    TARGET_HOST_HEADERS.computeIfAbsent(config.getHostname(), k -> new HashMap<>());
                    TARGET_HOST_HEADERS.get(config.getHostname()).put("Connection", "Keep-Alive");
                    TARGET_HOST_HEADERS.get(config.getHostname()).put("Keep-Alive", "timeout=" + config.getKeepAliveTime());
                }
            }

        }
    }

    private ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return ((httpResponse, httpContext) -> {
            HeaderElementIterator it = new BasicHeaderElementIterator(httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String name = he.getName();
                String value = he.getValue();
                if (value != null && "timeout".equalsIgnoreCase(name)) {
                    try {
                        return Long.parseLong(value) * 1000L;
                    } catch (NumberFormatException ignore) {
                        log.error("resolve Keep-Alive timeout", ignore);
                    }
                }
            }
            HttpHost target = (HttpHost) httpContext.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            log.info("connectionKeepAliveStrategy target host:{}", target.getHostName());
            //如果请求的目标地址单独做了配置,使用以下的设置
            Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpClientProperties.getKeepAliveTargetHost())
                    .orElseGet(HashMap::new)
                    .entrySet()
                    .stream()
                    .filter(e -> e.getKey().equalsIgnoreCase(target.getHostName()))
                    .findAny();
            int keepAliveTime = httpClientProperties.getKeepAliveTime() == null ? 60 : httpClientProperties.getKeepAliveTime();
            return any.map(e -> e.getValue() * 1000L).orElse(keepAliveTime * 1000L);
        });
    }

    private List<Header> genHeaders() {
        List<Header> headers = new ArrayList<>();
        if (httpClientProperties.getHeaders() == null) {
            log.warn("init header is null");
            return headers;
        }
        for (Map.Entry<String, String> entry : httpClientProperties.getHeaders().entrySet()) {
            headers.add(new BasicHeader(entry.getKey(), entry.getValue()));
        }
        return headers;
    }

    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                log.info("HttpMessageConvert exist null");
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = Charset.forName(httpClientProperties.getCharset());
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }

    @Bean
    @ConditionalOnMissingBean(value = {RestTemplate.class})
    public RestTemplate httpClientRestTemplate() {
        log.info("init httpClientRestTemplate.");
        RestTemplate restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory());
        modifyDefaultCharset(restTemplate);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }
}

  • 定义接口HttpClientComponent
package com.zzc.component.http;

import java.util.Map;

public interface HttpClientComponent {

    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式,若传入的header为空,则使用默认的header 请求header默认"Content-Type", "application/json;charset=utf-8"
     * @param body 请求消息体
     * @param clazz 返回类型
     * @return
     * @param <T>
     */
    <T> T post(String url, HttpClientHeaders headers, Object body, Class<T> clazz);


    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param body 请求消息体
     * @param clazz 返回类型
     * @return
     * @param <T>
     */
    <T> T post(String url, Object body, Class<T> clazz);


    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param body 请求消息体
     * @param clazz HttpClientResponseEntity<返回类型>
     * @return
     */
    <T> HttpClientResponseEntity<T> postEntity(String url, Object body, Class<T> clazz);

    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求headers
     * @param body 请求消息体
     * @param clazz HttpClientResponseEntity<返回类型>
     * @return
     */
    <T> HttpClientResponseEntity<T> postEntity(String url, HttpClientHeaders headers, Object body, Class<T> clazz);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式
     * @param uriVariables 不在url中的参数,将在该接口框架中进行处理
     * @param clazz 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Class<T> clazz);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式
     * @param clazz 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientHeaders headers, Class<T> clazz);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param clazz 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, Class<T> clazz);


    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param clazz 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, Class<T> clazz);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param headers 请求header
     * @param clazz 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Class<T> clazz);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param uriVariables url请求拼接的参数
     * @param clazz 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, Map<String, ?> uriVariables, Class<T> clazz);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param headers 请求header
     * @param uriVariables url参数
     * @param clazz 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Class<T> clazz);

    //-------------------------------------------------------------------------------------------------------------------

    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式,若传入的header为空,则使用默认的header 请求header默认"Content-Type", "application/json;charset=utf-8"
     * @param body 请求消息体
     * @param type 返回类型
     * @return
     * @param <T>
     */
    <T> T post(String url, HttpClientHeaders headers, Object body, Type<T> type);


    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param body 请求消息体
     * @param type 返回类型
     * @return
     * @param <T>
     */
    <T> T post(String url, Object body, Type<T> type);


    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param body 请求消息体
     * @param type HttpClientResponseEntity<返回类型>
     * @return
     */
    <T> HttpClientResponseEntity<T> postEntity(String url, Object body, Type<T> type);

    /**
     * post请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求headers
     * @param body 请求消息体
     * @param type HttpClientResponseEntity<返回类型>
     * @return
     */
    <T> HttpClientResponseEntity<T> postEntity(String url, HttpClientHeaders headers, Object body, Type<T> type);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式
     * @param uriVariables 不在url中的参数,将在该接口框架中进行处理
     * @param type 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Type<T> type);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers 请求头,key-value格式
     * @param type 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientHeaders headers, Type<T> type);

    /**
     * get请求
     * @param url 请求url,可以是域名,也可以是ip
     * @param type 返回类型
     * @return
     * @param <T>
     */
    <T> T get(String url, Type<T> type);


    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param type 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, Type<T> type);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param headers 请求header
     * @param type 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Type<T> type);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param uriVariables url请求拼接的参数
     * @param type 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, Map<String, ?> uriVariables, Type<T> type);

    /**
     * get请求
     * @param url url 请求url,可以是域名,也可以是ip
     * @param headers 请求header
     * @param uriVariables url参数
     * @param type 返回类型
     * @return
     */
    <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Type<T> type);


    //-------------------------------------------------------------------------------------------------------------------

    /**
     * get请求
     * 当对响应的内容需要进行解析时,使用该方法;例:文件下载,对response进行解析等
     * @param url 请求url,可以是域名,也可以是ip
     * @param extractor 响应回调对象,实现 extractData 接口并从中处理响应的 response
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientResponseExtractor<T> extractor);

    /**
     * get请求
     * 当对响应的内容需要进行解析时,使用该方法;例:文件下载,对response进行解析等
     * @param url 请求url,可以是域名,也可以是ip
     * @param headers
     * @param extractor 响应回调对象,实现 extractData 接口并从中处理响应的 response
     * @return
     * @param <T>
     */
    <T> T get(String url, HttpClientHeaders headers, HttpClientResponseExtractor<T> extractor);

    <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, HttpClientResponseExtractor<T> extractor);
}

  • 实现接口HttpClientComponentImpl
package com.zzc.component.http.httpclient;

import com.alibaba.fastjson2.JSON;
import com.zzc.component.http.HttpClientComponent;
import com.zzc.component.http.HttpClientHeaders;
import com.zzc.component.http.HttpClientResponse;
import com.zzc.component.http.HttpClientResponseEntity;
import com.zzc.component.http.HttpClientResponseExtractor;
import com.zzc.component.http.HttpClientStatus;
import com.zzc.component.http.Type;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Lazy
@Component
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@RequiredArgsConstructor
public class HttpClientComponentImpl implements HttpClientComponent {

    private final RestTemplate restTemplate;
    
    @Override
    public <T> T post(String url, HttpClientHeaders headers, Object body, Class<T> clazz) {
        log.debug("rest post url:{}, headers:{}, body:{}", url, headers, body);
        HttpClientResponseEntity<?> response = postEntity(url, headers, body, clazz);
        if (response != null) {
            return (T) response.getBody();
        }
        return null;
    }

    @Override
    public <T> T post(String url, Object body, Class<T> clazz) {
        return post(url, null, body, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T> postEntity(String url, Object body, Class<T> clazz) {
        return postEntity(url, null, body, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T> postEntity(String url, HttpClientHeaders headers, Object body, Class<T> clazz) {
        HttpEntity<Object> formEntity = new HttpEntity<>(body, generateHeader(headers));
        try {
            ResponseEntity<T> response = restTemplate.postForEntity(url, formEntity, clazz);
            if (response == null) {
                log.error("request error, url:{}", url);
                return null;
            }
            return new HttpClientResponseEntity<T>(response.getBody(), new HttpClientHeaders(response.getHeaders().toSingleValueMap()), HttpClientStatus.resolve(response.getStatusCodeValue()));
        } catch (Exception e) {
            log.error("post error. url:{}, headers:{}, body:{}", url, headers, body, e);
            throw e;
        }
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Class<T> clazz) {
        HttpClientResponseEntity<?> response = getEntity(url, headers, uriVariables, clazz);
        if (response == null) {
            return null;
        }
        return (T) response.getBody();
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, Class<T> clazz) {
        return get(url, headers, null, clazz);
    }

    @Override
    public <T> T get(String url, Class<T> clazz) {
        return get(url, null, null, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T>  getEntity(String url, Class<T>  clazz) {
        return getEntity(url, null, null, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T>  getEntity(String url, HttpClientHeaders headers, Class<T>  clazz) {
        return getEntity(url, headers, null, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T>  getEntity(String url, Map<String, ?> uriVariables, Class<T>  clazz) {
        return getEntity(url, null, uriVariables, clazz);
    }

    @Override
    public <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Class<T> clazz) {
        try {
            ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, generateHeader(headers)), clazz, uriVariables == null ? new HashMap<>() : uriVariables);
            if (response == null) {
                log.error("request error, url:{}", url);
                return null;
            }
            return new HttpClientResponseEntity<>(response.getBody(), new HttpClientHeaders(response.getHeaders().toSingleValueMap()), HttpClientStatus.resolve(response.getStatusCodeValue()));
        } catch (Exception e) {
            log.error("get error. url:{}, headers:{}, uriVariables:{}", url, headers, uriVariables, e);
            throw e;
        }
    }

    @Override
    public <T> T post(String url, HttpClientHeaders headers, Object body, Type<T> type) {
        HttpClientResponseEntity<T> response = postEntity(url, headers, body, type);
        if (response == null) {
            return null;
        }
        return response.getBody();
    }

    @Override
    public <T> T post(String url, Object body, Type<T> type) {
        return post(url, null, body, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> postEntity(String url, Object body, Type<T> type) {
        return postEntity(url, null, body, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> postEntity(String url, HttpClientHeaders headers, Object body, Type<T> type) {
        HttpEntity<Object> formEntity = new HttpEntity<>(body, generateHeader(headers));
        try {
            ResponseEntity<String> response = restTemplate.postForEntity(url, formEntity, String.class);
            if (response == null) {
                log.error("request error, url:{}", url);
                return null;
            }
            return new HttpClientResponseEntity<T>(parse(response.getBody(), type), new HttpClientHeaders(response.getHeaders().toSingleValueMap()), HttpClientStatus.resolve(response.getStatusCodeValue()));
        } catch (Exception e) {
            log.error("post error. url:{}, headers:{}, body:{}", url, headers, body, e);
            throw e;
        }
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Type<T> type) {
        HttpClientResponseEntity<T> response = getEntity(url, headers, uriVariables, type);
        if (response == null) {
            return null;
        }
        return response.getBody();
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, Type<T> type) {
        return get(url, headers, null, type);
    }

    @Override
    public <T> T get(String url, Type<T> type) {
        return get(url, null,null, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> getEntity(String url, Type<T> type) {
        return getEntity(url, null, null, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Type<T> type) {
        return getEntity(url, headers, null, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> getEntity(String url, Map<String, ?> uriVariables, Type<T> type) {
        return getEntity(url, null, uriVariables, type);
    }

    @Override
    public <T> HttpClientResponseEntity<T> getEntity(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, Type<T> type) {
        try {
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, generateHeader(headers)), String.class, uriVariables == null ? new HashMap<>() : uriVariables);
            if (response == null) {
                log.error("request error, url:{}", url);
                return null;
            }
            return new HttpClientResponseEntity<T>(parse(response.getBody(), type), new HttpClientHeaders(response.getHeaders().toSingleValueMap()), HttpClientStatus.resolve(response.getStatusCodeValue()));
        } catch (Exception e) {
            log.error("get error. url:{}, headers:{}, uriVariables:{}", url, headers, uriVariables, e);
            throw e;
        }
    }

    @Override
    public <T> T get(String url, HttpClientResponseExtractor<T> extractor) {
        return get(url, null, null, extractor);
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, HttpClientResponseExtractor<T> extractor) {
        return get(url, headers, null, extractor);
    }

    @Override
    public <T> T get(String url, HttpClientHeaders headers, Map<String, ?> uriVariables, HttpClientResponseExtractor<T> extractor) {
        RequestCallback requestCallback = null;
        if (headers == null || headers.isEmpty()) {
            requestCallback = restTemplate.httpEntityCallback(HttpEntity.EMPTY);
        } else {
            HttpEntity<Map<String, Object>> formEntity = new HttpEntity<>(null, generateHeader(headers));
            requestCallback = restTemplate.httpEntityCallback(formEntity);
        }
        ResponseExtractor<T> responseExtractor = new ResponseExtractor<T>() {
            @Override
            public T extractData(ClientHttpResponse response) throws IOException {

                HttpClientResponse httpClientResponse = new HttpClientResponse() {
                    @Override
                    public InputStream getBody() throws IOException {
                        return response.getBody();
                    }

                    @Override
                    public HttpClientStatus getStatusCode() throws IOException {
                        return HttpClientStatus.resolve(response.getStatusCode().value());
                    }

                    @Override
                    public int getRawStatusCode() throws IOException {
                        return response.getRawStatusCode();
                    }

                    @Override
                    public String getStatusText() throws IOException {
                        return response.getStatusText();
                    }

                    @Override
                    public void close() {
                        response.close();
                    }
                };
                return extractor.extractData(httpClientResponse);
            }
        };
        if (uriVariables != null && !uriVariables.isEmpty()) {//URI expanded = getUriTemplateHandler().expand(url, uriVariables); 对uriVariables不做判空处理,等自己判断
            return restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
        } else {
            return restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
        }

    }

    /**
     * 则需参数接收的载体需要使用 MultiValueMap
     * @return
     */
    private static MultiValueMap<String, String> defaultHeaders() {
        return new HttpHeaders();
    }

    private static MultiValueMap<String, String> generateHeader(Map<String, String> headerMap) {
        if (headerMap == null || headerMap.isEmpty()) {
            return defaultHeaders();
        }
        MultiValueMap<String, String> headers = new HttpHeaders();
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            List<String> objList = new ArrayList<>();
            objList.add(entry.getValue());
            headers.put(entry.getKey(), objList);
        }
        return headers;
    }

    private <T> T parse(String bodyStr, Type<T> type) {
        return JSON.parseObject(bodyStr, type);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2281791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

填坑 kali 虚拟机磁盘耗尽

今天准备SSH暴力破解&#xff0c;打开kali 虚拟机的时候&#xff0c;发现一直卡在一个地方不动&#xff0c;一开始的时候&#xff0c;我并不知道是磁盘满了&#xff0c;只是跟着网上的解决方案去实施操作&#xff0c;也是通过recovery mode&#xff0c;然后一通操作拿到shell&a…

智能手机“混战”2025:谁将倒下而谁又将突围?

【潮汐商业评论原创】 “去年做手机比较艰难&#xff0c;几乎每个品牌都在调价、压货&#xff0c;像华为这种以前都不给我们分货的厂商&#xff0c;也开始成为我的主要库存。不过今年开头比较好&#xff0c;20号国补一开始&#xff0c;店里的人流和手机销量就明显涨了不少&…

MongoDB文档查询

一、实验目的 1. 理解MongoDB文档数据库的基本概念和特性。 2. 掌握在MongoDB中创建集合和插入文档数据的方法。 3. 学习使用MongoDB进行文档查询操作&#xff0c;包括查询、过滤和排序等。 二、实验环境准备 1. JAVA环境准备&#xff1a;确保Java Development Kit (J…

【Python・机器学习】多元回归模型(原理及代码)

前言 自学笔记&#xff0c;分享给语言学/语言教育学方向的&#xff0c;但对语言数据处理感兴趣但是尚未入门&#xff0c;却需要在论文中用到的小伙伴&#xff0c;欢迎大佬们补充或绕道。ps&#xff1a;本文最少限度涉及公式讲解&#xff08;文科生小白友好体质&#xff09;&am…

Vue中设置报错页面和“Uncaught runtime errors”弹窗关闭

文章目录 前言操作步骤大纲1.使用Vue自带的报错捕获机制添加报错信息2.在接口报错部分添加相同机制3.把报错信息添加到Vuex中方便全局使用4.添加报错页面备用5.app页面添加if判断替换报错界面 效果备注&#xff1a;vue项目中Uncaught runtime errors:怎样关闭 前言 在开发Vue项…

Adobe的AI生成3D数字人框架:从自拍到生动的3D化身

一、引言 随着人工智能技术的发展,我们见证了越来越多创新工具的出现,这些工具使得图像处理和视频编辑变得更加智能与高效。Adobe作为全球领先的创意软件公司,最近推出了一项令人瞩目的新技术——一个能够将普通的二维自拍照转换成栩栩如生的三维(3D)数字人的框架。这项技…

2025.1.20——四、[强网杯 2019]Upload1 文件上传|反序列化

题目来源&#xff1a;buuctf [强网杯 2019]Upload 1 目录 一、打开靶机&#xff0c;查看信息 二、解题思路 step 1&#xff1a;登陆进去看情况 step 2&#xff1a;大佬来支援——问题在cookie step 3&#xff1a;测试两个思路 1.目录穿越 2.目录扫描 step 4&#xff…

Docker—搭建Harbor和阿里云私有仓库

Harbor概述 Harbor是一个开源的企业级Docker Registry管理项目&#xff0c;由VMware公司开发。‌它的主要用途是帮助用户迅速搭建一个企业级的Docker Registry服务&#xff0c;提供比Docker官方公共镜像仓库更为丰富和安全的功能&#xff0c;特别适合企业环境使用。‌12 Harb…

组播IGMP协议报文介绍

1 IGMP协议 1.1 定义 IGMP&#xff08;Internet Group Management Protocol&#xff09;是因特网协议家族中的一个组播协议&#xff0c;它共有三个版本&#xff1a;v1、v2和v3。 IGMPv1中定义了基本的组成员查询和报告过程&#xff0c;IGMPv2在此基础上添加了查询器选举和组…

hedfs和hive数据迁移后校验脚本

先谈论校验方法&#xff0c;本人腾讯云大数据工程师。 1、hdfs的校验 这个通常就是distcp校验&#xff0c;hdfs通过distcp迁移到另一个集群&#xff0c;怎么校验你的对不对。 有人会说&#xff0c;默认会有校验CRC校验。我们关闭了&#xff0c;为什么关闭&#xff1f;全量迁…

性能优化之动态加载

在过去近三十年的职业生涯里&#xff0c;有几年专注于运行时环境的开发与实现。在runtime中&#xff0c;动态加载技术是其中的基石之一。动态加载技术是指在系统运行过程中&#xff0c;根据需要把程序和数据从外存或网络加载到内存中的过程。其中&#xff0c;lazy loading&…

数据从前端传到后端入库过程分析

数据从前端传到后端入库过程分析 概述 积累了一些项目经验&#xff0c;成长为一个老程序员了&#xff0c;自认为对各种业务和技术都能得心应手的应对了&#xff0c;殊不知很多时候我们借助了搜索引擎的能力&#xff0c;当然现在大家都是通过AI来武装自己。 今天要分析的话题是…

【线性代数】列主元法求矩阵的逆

列主元方法是一种用于求解矩阵逆的数值方法&#xff0c;特别适用于在计算机上实现。其基本思想是通过高斯消元法将矩阵转换为上三角矩阵&#xff0c;然后通过回代求解矩阵的逆。以下是列主元方法求解矩阵 A A A 的逆的步骤&#xff1a; [精确算法] 列主元高斯消元法 步骤 1&am…

LabVIEW太赫兹二维扫描成像系统

使用LabVIEW设计太赫兹二维扫描成像系统。通过LabVIEW平台开发&#xff0c;结合硬件如太赫兹源、平移台、锁相放大器等&#xff0c;实现了高效、精准的成像功能。系统采用蛇形扫描方式&#xff0c;通过动态调整扫描参数&#xff0c;达到优化成像质量的目的。 ​ 项目背景 在非…

Quartus:开发使用及 Tips 总结

Quartus是Altera&#xff08;现已被Intel收购&#xff09;推出的一款针对其FPGA产品的综合性开发环境&#xff0c;用于设计、仿真和调试数字电路。以下是使用Quartus的一些总结和技巧(Tips)&#xff0c;帮助更高效地进行FPGA项目开发&#xff1a; 这里写目录标题 使用总结TIPS…

Android 自定义View时四个构造函数使用详解

该文章我们以自定义View继承TextView为例来讲解 创建自定义View命名MyTextView&#xff0c;并使其继承TextView 1、自定义View时第一个构造函数 // 第一个构造函数主要是在Java代码中声明一个MyTextView时所用 // 类似这种(MyTextView myTextViewnew MyTextView(this);) // 不…

C#PaddleOCRSharp使用

using PaddleOCRSharp;namespace PaddleOCRSharpDemo {internal class Program{static void Main(string[] args){//中英文模型V3模型OCRModelConfig config null;//OCR参数OCRParameter oCRParameter new OCRParameter();oCRParameter.cpu_math_library_num_threads 6;//预…

vscode配置C/C++环境(详细步骤教程)

本章教程,主要介绍如何在vscode中配置c/c++环境的具体步骤。 一、安装mingw64 链接:https://pan.baidu.com/s/1fwS-CwC7dgIYJTanaINOhA?pwd=rdks 提取码:rdks 下载之后,配置将mingw64添加到系统环境变量中。 二、安装vscode插件 需要在vscode插件商店,安装c/c++插件 三、配…

隐私保护+性能优化,RyTuneX 让你的电脑更快更安全

RyTuneX 是一款专为 Windows 10 和 11 用户量身打造的系统优化工具&#xff0c;采用先进的 WinUI 3 框架开发&#xff0c;以其现代化的设计风格和强大的功能集合脱颖而出。这款工具不仅界面简洁美观&#xff0c;还提供了多样化的系统优化选项&#xff0c;旨在帮助用户最大化设备…

JAVA:Spring WebClient 的应用指南

1、简述 随着微服务架构的普及&#xff0c;服务间的 HTTP 通信需求也越来越多。Spring 提供的 WebClient 是 RestTemplate 的替代方案&#xff0c;支持响应式编程&#xff0c;具有非阻塞的特点&#xff0c;非常适合处理高并发的 HTTP 请求。本文将介绍 WebClient 的基本用法及…