package com.cyksj.common.util;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
/**
* debug jvm params:
* 1. httpClient:
* -Djdk.internal.httpclient.debug=true
* 2. webSocket:
* -Djdk.internal.httpclient.websocket.debug=true
* 3. hpack
* -Djdk.internal.httpclient.hpack.debug=true
*
*
* jdk11 Http Client is Thread-safe
* Build connection pool and selector thread pool internally
*
*
* @author valor.
*/
public class J11HttpC {
private J11HttpC() { }
private static final J11HttpC instance;
static {
instance = new J11HttpC();
}
/* ---------- register http client (Editable) ---------- */
/**
* 连接超时时间
*/
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(4);
/**
* 读取超时时间
*/
private static final Duration SOCKET_TIMEOUT = Duration.ofSeconds(6);
/**
* Thread-safe
*/
private static final HttpClient defaultClient;
static {
defaultClient = HttpClient.newBuilder()
.connectTimeout(CONNECT_TIMEOUT)
.build();
}
/**
* 微信退款 专用 ssl http client
*/
private static final HttpClient wx_refund;
static {
wx_refund = HttpClient.newBuilder()
.connectTimeout(CONNECT_TIMEOUT)
.sslContext(NSslKit.KeyCert.wechat_refund.getSslContext())
.build();
}
/**
* http clients
*/
public enum Client {
BASE(defaultClient),
WX_REFUND(wx_refund),
;
private HttpClient symbol;
Client(HttpClient symbol) {
this.symbol = symbol;
}
}
/* ---------- http request builder ---------- */
public static Builder custom() {
return new Builder();
}
public static class Builder {
private String url;
private Method method;
private KVAdapter[] headers;
private HttpRequest.BodyPublisher bodyPublisher;
/**
* cache URI
*/
private boolean cacheURI;
private Builder() {
this.cacheURI = false;
}
public Builder url(String url) {
this.url = url;
return this;
}
public Builder cacheURI() {
this.cacheURI = true;
return this;
}
public Builder method(Method method) {
this.method = method;
return this;
}
public Builder ofGet() {
this.method = Method.GET;
return this;
}
public Builder ofPost() {
this.method = Method.POST;
return this;
}
public Builder headers(ReqType reqType) {
this.headers = new KVAdapter[] {ofHeader("content-type", reqType.mime)};
return this;
}
public Builder headers(KVAdapter... headers) {
this.headers = headers;
return this;
}
public Builder headers(ReqType reqType, KVAdapter... headers) {
final int oldLength = headers.length;
final int newLength = oldLength+ 1;
KVAdapter[] newHeaders = new KVAdapter[newLength];
System.arraycopy(headers, 0, newHeaders, 1, oldLength);
newHeaders[0] = ofHeader("content-type", reqType.mime);
this.headers = newHeaders;
return this;
}
public Builder body(HttpRequest.BodyPublisher bodyPublisher) {
this.bodyPublisher = bodyPublisher;
return this;
}
/* --- 同步send --- */
public HttpResponse send(HttpResponse.BodyHandler responseBodyHandler) throws IOException, InterruptedException {
return this.send(Client.BASE, responseBodyHandler);
}
public HttpResponse send(Client httpClient, HttpResponse.BodyHandler responseBodyHandler) throws IOException, InterruptedException {
return instance.send(httpClient.symbol, this.getRequest(), responseBodyHandler);
}
/* --- 异步send --- */
public CompletableFuture> sendAsync(HttpResponse.BodyHandler responseBodyHandler) {
return this.sendAsync(Client.BASE, responseBodyHandler);
}
public CompletableFuture> sendAsync(Client httpClient, HttpResponse.BodyHandler responseBodyHandler) {
return instance.sendAsync(httpClient.symbol, this.getRequest(), responseBodyHandler);
}
/* --- ------- --- */
private HttpRequest getRequest() {
// require http URL
if (StringUtils.isBlank(this.url)) {
throw new RuntimeException("Empty Http URL.");
}
// default http method: GET
if (this.method == null) {
this.method = Method.GET;
}
// default http request body: noBody
if (this.bodyPublisher == null) {
this.bodyPublisher = HttpRequest.BodyPublishers.noBody();
}
URI uri = getURI(this.url, this.cacheURI);
HttpRequest request;
if (this.headers == null || this.headers.length <= 0) {
request = instance.buildRequest(uri, this.method, this.bodyPublisher);
} else {
request = instance.buildRequest(uri, this.method, this.bodyPublisher, this.headers);
}
return request;
}
}
/* ---------- cache http request uri ---------- */
/**
* 缓存URI的数量
*/
// private static final int URI_CACHE_SIZE = 25;
/**
* 减少URI类的解析 LRU cache
*/
// private static volatile LruCache uriCache = new LruCache<>(URI_CACHE_SIZE);
/**
* 获取URI
*/
private static URI getURI(String url, boolean cache) {
// 非常频繁的接口调用 会导致cache维护开销增加
// if (cache) {
// URI uri = uriCache.get(url);
// if (null == uri) {
// uri = URI.create(url);
// uriCache.put(url, uri);
// }
// return uri;
// }
return URI.create(url);
}
/* ---------- pack http client ---------- */
/**
* 构建request
*
* @param uri {@link URI}
* @param headers {@link J11HttpC.KVAdapter}
* @param httpMethod {@link J11HttpC.Method}
* @param bodyPublisher {@link HttpRequest.BodyPublisher}
* @return {@link HttpRequest}
*/
private HttpRequest buildRequest(URI uri,
Method httpMethod,
HttpRequest.BodyPublisher bodyPublisher,
KVAdapter... headers) {
HttpRequest.Builder builder = HttpRequest.newBuilder();
builder.timeout(SOCKET_TIMEOUT);
builder.uri(uri);
// 设置请求头
if (headers.length > 0) {
for (KVAdapter e : headers) {
builder.setHeader(e.name, e.value);
}
}
// 设置请求方式和body参数
builder.method(httpMethod.symbol, bodyPublisher);
return builder.build();
}
/**
* 同步send
*
* @see HttpClient#send(HttpRequest, HttpResponse.BodyHandler)
*/
public HttpResponse
send(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler responseBodyHandler)
throws IOException, InterruptedException {
return httpClient.send(request, responseBodyHandler);
}
/**
* 同步send
*
* @see HttpClient#sendAsync(HttpRequest, HttpResponse.BodyHandler)
*/
public CompletableFuture>
sendAsync(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler responseBodyHandler) {
return httpClient.sendAsync(request, responseBodyHandler);
}
/* ---------- http extensions ---------- */
public static KVAdapter ofHeader(String name, String value) {
return new KVAdapter(name, value);
}
/**
* Http Request Header
*/
public static final class KVAdapter {
private String name;
private String value;
private KVAdapter(String name, String value) {
this.name = name;
this.value = value;
}
}
public enum Method {
GET("GET"),
POST("POST"),
HEAD("HEAD"),
OPTIONS("OPTIONS"),
PUT("PUT"),
DELETE("DELETE"),
TRACE("TRACE");
private String symbol;
Method(String symbol) {
this.symbol = symbol;
}
}
public enum ReqType {
// form_data("multipart/form-data"),
form_urlencoded("application/x-www-form-urlencoded; charset=utf-8"),
raw_text("text/plain; charset=utf-8"),
raw_js("application/javascript; charset=utf-8"),
raw_json("application/json; charset=utf-8"),
raw_html("text/html; charset=utf-8"),
raw_xml("application/xml; charset=utf-8");
// binary("application/octet-stream");
private String mime;
ReqType(String mime) {
this.mime = mime;
}
}
}