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