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&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&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&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);
}
}