J11HttpC.java 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. package com.cyksj.common.util;
  2. import org.apache.commons.lang3.StringUtils;
  3. import java.io.IOException;
  4. import java.net.URI;
  5. import java.net.http.HttpClient;
  6. import java.net.http.HttpRequest;
  7. import java.net.http.HttpResponse;
  8. import java.time.Duration;
  9. import java.util.concurrent.CompletableFuture;
  10. /**
  11. * debug jvm params:
  12. * 1. httpClient:
  13. * -Djdk.internal.httpclient.debug=true
  14. * 2. webSocket:
  15. * -Djdk.internal.httpclient.websocket.debug=true
  16. * 3. hpack
  17. * -Djdk.internal.httpclient.hpack.debug=true
  18. *
  19. * <p>
  20. * jdk11 Http Client is Thread-safe
  21. * Build connection pool and selector thread pool internally
  22. * </p>
  23. *
  24. * @author valor.
  25. */
  26. public class J11HttpC {
  27. private J11HttpC() { }
  28. private static final J11HttpC instance;
  29. static {
  30. instance = new J11HttpC();
  31. }
  32. /* ---------- register http client (Editable) ---------- */
  33. /**
  34. * 连接超时时间
  35. */
  36. private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(4);
  37. /**
  38. * 读取超时时间
  39. */
  40. private static final Duration SOCKET_TIMEOUT = Duration.ofSeconds(6);
  41. /**
  42. * Thread-safe
  43. */
  44. private static final HttpClient defaultClient;
  45. static {
  46. defaultClient = HttpClient.newBuilder()
  47. .connectTimeout(CONNECT_TIMEOUT)
  48. .build();
  49. }
  50. /**
  51. * 微信退款 专用 ssl http client
  52. */
  53. private static final HttpClient wx_refund;
  54. static {
  55. wx_refund = HttpClient.newBuilder()
  56. .connectTimeout(CONNECT_TIMEOUT)
  57. .sslContext(NSslKit.KeyCert.wechat_refund.getSslContext())
  58. .build();
  59. }
  60. /**
  61. * http clients
  62. */
  63. public enum Client {
  64. BASE(defaultClient),
  65. WX_REFUND(wx_refund),
  66. ;
  67. private HttpClient symbol;
  68. Client(HttpClient symbol) {
  69. this.symbol = symbol;
  70. }
  71. }
  72. /* ---------- http request builder ---------- */
  73. public static Builder custom() {
  74. return new Builder();
  75. }
  76. public static class Builder {
  77. private String url;
  78. private Method method;
  79. private KVAdapter[] headers;
  80. private HttpRequest.BodyPublisher bodyPublisher;
  81. /**
  82. * cache URI
  83. */
  84. private boolean cacheURI;
  85. private Builder() {
  86. this.cacheURI = false;
  87. }
  88. public Builder url(String url) {
  89. this.url = url;
  90. return this;
  91. }
  92. public Builder cacheURI() {
  93. this.cacheURI = true;
  94. return this;
  95. }
  96. public Builder method(Method method) {
  97. this.method = method;
  98. return this;
  99. }
  100. public Builder ofGet() {
  101. this.method = Method.GET;
  102. return this;
  103. }
  104. public Builder ofPost() {
  105. this.method = Method.POST;
  106. return this;
  107. }
  108. public Builder headers(ReqType reqType) {
  109. this.headers = new KVAdapter[] {ofHeader("content-type", reqType.mime)};
  110. return this;
  111. }
  112. public Builder headers(KVAdapter... headers) {
  113. this.headers = headers;
  114. return this;
  115. }
  116. public Builder headers(ReqType reqType, KVAdapter... headers) {
  117. final int oldLength = headers.length;
  118. final int newLength = oldLength+ 1;
  119. KVAdapter[] newHeaders = new KVAdapter[newLength];
  120. System.arraycopy(headers, 0, newHeaders, 1, oldLength);
  121. newHeaders[0] = ofHeader("content-type", reqType.mime);
  122. this.headers = newHeaders;
  123. return this;
  124. }
  125. public Builder body(HttpRequest.BodyPublisher bodyPublisher) {
  126. this.bodyPublisher = bodyPublisher;
  127. return this;
  128. }
  129. /* --- 同步send --- */
  130. public <T> HttpResponse<T> send(HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
  131. return this.send(Client.BASE, responseBodyHandler);
  132. }
  133. public <T> HttpResponse<T> send(Client httpClient, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
  134. return instance.send(httpClient.symbol, this.getRequest(), responseBodyHandler);
  135. }
  136. /* --- 异步send --- */
  137. public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpResponse.BodyHandler<T> responseBodyHandler) {
  138. return this.sendAsync(Client.BASE, responseBodyHandler);
  139. }
  140. public <T> CompletableFuture<HttpResponse<T>> sendAsync(Client httpClient, HttpResponse.BodyHandler<T> responseBodyHandler) {
  141. return instance.sendAsync(httpClient.symbol, this.getRequest(), responseBodyHandler);
  142. }
  143. /* --- ------- --- */
  144. private HttpRequest getRequest() {
  145. // require http URL
  146. if (StringUtils.isBlank(this.url)) {
  147. throw new RuntimeException("Empty Http URL.");
  148. }
  149. // default http method: GET
  150. if (this.method == null) {
  151. this.method = Method.GET;
  152. }
  153. // default http request body: noBody
  154. if (this.bodyPublisher == null) {
  155. this.bodyPublisher = HttpRequest.BodyPublishers.noBody();
  156. }
  157. URI uri = getURI(this.url, this.cacheURI);
  158. HttpRequest request;
  159. if (this.headers == null || this.headers.length <= 0) {
  160. request = instance.buildRequest(uri, this.method, this.bodyPublisher);
  161. } else {
  162. request = instance.buildRequest(uri, this.method, this.bodyPublisher, this.headers);
  163. }
  164. return request;
  165. }
  166. }
  167. /* ---------- cache http request uri ---------- */
  168. /**
  169. * 缓存URI的数量
  170. */
  171. // private static final int URI_CACHE_SIZE = 25;
  172. /**
  173. * 减少URI类的解析 LRU cache
  174. */
  175. // private static volatile LruCache<String, URI> uriCache = new LruCache<>(URI_CACHE_SIZE);
  176. /**
  177. * 获取URI
  178. */
  179. private static URI getURI(String url, boolean cache) {
  180. // 非常频繁的接口调用 会导致cache维护开销增加
  181. // if (cache) {
  182. // URI uri = uriCache.get(url);
  183. // if (null == uri) {
  184. // uri = URI.create(url);
  185. // uriCache.put(url, uri);
  186. // }
  187. // return uri;
  188. // }
  189. return URI.create(url);
  190. }
  191. /* ---------- pack http client ---------- */
  192. /**
  193. * 构建request
  194. *
  195. * @param uri {@link URI}
  196. * @param headers {@link J11HttpC.KVAdapter}
  197. * @param httpMethod {@link J11HttpC.Method}
  198. * @param bodyPublisher {@link HttpRequest.BodyPublisher}
  199. * @return {@link HttpRequest}
  200. */
  201. private HttpRequest buildRequest(URI uri,
  202. Method httpMethod,
  203. HttpRequest.BodyPublisher bodyPublisher,
  204. KVAdapter... headers) {
  205. HttpRequest.Builder builder = HttpRequest.newBuilder();
  206. builder.timeout(SOCKET_TIMEOUT);
  207. builder.uri(uri);
  208. // 设置请求头
  209. if (headers.length > 0) {
  210. for (KVAdapter e : headers) {
  211. builder.setHeader(e.name, e.value);
  212. }
  213. }
  214. // 设置请求方式和body参数
  215. builder.method(httpMethod.symbol, bodyPublisher);
  216. return builder.build();
  217. }
  218. /**
  219. * 同步send
  220. *
  221. * @see HttpClient#send(HttpRequest, HttpResponse.BodyHandler)
  222. */
  223. public <T> HttpResponse<T>
  224. send(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
  225. throws IOException, InterruptedException {
  226. return httpClient.send(request, responseBodyHandler);
  227. }
  228. /**
  229. * 同步send
  230. *
  231. * @see HttpClient#sendAsync(HttpRequest, HttpResponse.BodyHandler)
  232. */
  233. public <T> CompletableFuture<HttpResponse<T>>
  234. sendAsync(HttpClient httpClient, HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) {
  235. return httpClient.sendAsync(request, responseBodyHandler);
  236. }
  237. /* ---------- http extensions ---------- */
  238. public static KVAdapter ofHeader(String name, String value) {
  239. return new KVAdapter(name, value);
  240. }
  241. /**
  242. * Http Request Header
  243. */
  244. public static final class KVAdapter {
  245. private String name;
  246. private String value;
  247. private KVAdapter(String name, String value) {
  248. this.name = name;
  249. this.value = value;
  250. }
  251. }
  252. public enum Method {
  253. GET("GET"),
  254. POST("POST"),
  255. HEAD("HEAD"),
  256. OPTIONS("OPTIONS"),
  257. PUT("PUT"),
  258. DELETE("DELETE"),
  259. TRACE("TRACE");
  260. private String symbol;
  261. Method(String symbol) {
  262. this.symbol = symbol;
  263. }
  264. }
  265. public enum ReqType {
  266. // form_data("multipart/form-data"),
  267. form_urlencoded("application/x-www-form-urlencoded; charset=utf-8"),
  268. raw_text("text/plain; charset=utf-8"),
  269. raw_js("application/javascript; charset=utf-8"),
  270. raw_json("application/json; charset=utf-8"),
  271. raw_html("text/html; charset=utf-8"),
  272. raw_xml("application/xml; charset=utf-8");
  273. // binary("application/octet-stream");
  274. private String mime;
  275. ReqType(String mime) {
  276. this.mime = mime;
  277. }
  278. }
  279. }