package org.ruoyi.system.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.*; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; 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.entity.*; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import java.io.*; import java.net.HttpURLConnection; import java.net.SocketException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; /** * HttpUtils * * @author NSL * @since 2024-12-30 */ public class HttpUtils { public static final String CHARSET_DEFAULT = "UTF-8"; public static final String CONTENT_TYPE = "Content-Type"; public static final String CONTENT_TYPE_JSON = "application/json"; public static final String CONTENT_TYPE_FORM_DATA = "application/x-www-form-urlencoded"; private static final PoolingHttpClientConnectionManager CONNECTION_MANAGER; private static final int MAX_CONNECT_TIMEOUT = 8000; private static final int MAX_SOCKET_TIMEOUT = 90000; static { CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(getDefaultRegistry()); CONNECTION_MANAGER.setMaxTotal(500); CONNECTION_MANAGER.setDefaultMaxPerRoute(50); CONNECTION_MANAGER.setValidateAfterInactivity(2000); } public static HttpResponse get(String url) { return get(url, null); } public static HttpResponse get(String url, Map params) { String urlLinks = getUrlLinks(params); if (urlLinks != null) { if (url.contains("?")) { url = url + "&" + urlLinks; } else { url = url + "?" + urlLinks; } } return request(HttpRequest.build(url, "GET")); } public static void download(String url, File destFile) throws Exception { HttpRequest request = HttpRequest.build(url, "GET"); request.setResponseHandler(entity -> { try { if (!destFile.getParentFile().exists()) { destFile.getParentFile().mkdirs(); } try (FileOutputStream fs = new FileOutputStream(destFile)) { entity.writeTo(fs); } return destFile; } catch (Exception e) { return e; } }); HttpResponse response = request(request); if (response.getResponse() instanceof Exception) { throw (Exception) response.getResponse(); } } public static HttpResponse postJson(String url, String bodyJson) { HttpRequest request = HttpRequest.build(url, "POST").setBody(bodyJson); if (request.getHeaders() == null) { request.setHeaders(Collections.singletonMap(CONTENT_TYPE, CONTENT_TYPE_JSON)); } else { request.getHeaders().put(CONTENT_TYPE, CONTENT_TYPE_JSON); } return request(request); } public static HttpResponse postForm(String url, Map params) { String urlLinks = getUrlLinks(params); HttpRequest request = HttpRequest.build(url, "POST").setBody(urlLinks != null ? urlLinks : ""); if (request.getHeaders() == null) { request.setHeaders(Collections.singletonMap(CONTENT_TYPE, CONTENT_TYPE_FORM_DATA)); } else { request.getHeaders().put(CONTENT_TYPE, CONTENT_TYPE_FORM_DATA); } return request(request); } public static HttpResponse requestWithEventStream(ExecutorService executorService, long firstReadTimeout, HttpRequest request, Consumer dataConsumer) throws ExecutionException, InterruptedException, TimeoutException { return requestWithEventStream(executorService, firstReadTimeout, request, dataConsumer, null); } public static HttpResponse requestWithEventStream(ExecutorService executorService, long firstReadTimeout, HttpRequest request, Consumer dataConsumer, Consumer> futureConsumer) throws ExecutionException, InterruptedException, TimeoutException { // status: 0 start 1 run 2 timeout AtomicInteger status = new AtomicInteger(0); Future submit = executorService.submit(() -> { if (request.getMaxSocketTimeout() == null) { request.setMaxSocketTimeout(30_000); } request.setResponseHandler(entity -> { StringBuilder sb = new StringBuilder(); try { try (InputStream is = entity.getContent()) { try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String line; while ((line = bufferedReader.readLine()) != null) { if (status.get() == 2) { throw new TimeoutException(); } status.set(1); sb.append(line).append("\n"); if (line.startsWith("data:")) { dataConsumer.accept(line.substring(line.startsWith("data: ") ? 6 : 5)); } } } } } catch (Exception e) { throw new RuntimeException("eventStream 请求异常", e); } return sb.toString(); }); return request(request); }); if (futureConsumer != null) { futureConsumer.accept(submit); } try { HttpResponse httpResponse = submit.get(firstReadTimeout, TimeUnit.MILLISECONDS); if (httpResponse != null) { return httpResponse; } } catch (TimeoutException e) { if (status.get() == 0) { status.set(2); submit.cancel(true); throw e; } } return submit.get(); } public static HttpResponse requestWithEventStream(HttpRequest request, Consumer dataConsumer) { if (request.getMaxSocketTimeout() == null) { request.setMaxSocketTimeout(30_000); } request.setResponseHandler(entity -> { StringBuilder sb = new StringBuilder(); try { try (InputStream is = entity.getContent()) { try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String line; while ((line = bufferedReader.readLine()) != null) { sb.append(line).append("\n"); if (line.startsWith("data:")) { dataConsumer.accept(line.substring(line.startsWith("data: ") ? 6 : 5)); } } } } } catch (Exception e) { throw new RuntimeException("eventStream 请求异常", e); } return sb.toString(); }); return request(request); } public static int getUrlHttpStatus(String _url) { HttpURLConnection urlConnection = null; try { URL url = new URL(_url); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.connect(); return urlConnection.getResponseCode(); } catch (Exception ignored) { return -1; } finally { if (urlConnection != null) { urlConnection.disconnect(); } } } public static String getUrlLinks(Map params) { if (params == null || params.isEmpty()) { return null; } StringBuilder sb = new StringBuilder(); try { String[] sortedKeys = params.keySet().toArray(new String[0]); Arrays.sort(sortedKeys); for (String key : sortedKeys) { if (key == null || key.isEmpty()) { continue; } Object value = params.get(key); sb.append(key).append("="); if (value != null) { sb.append(URLEncoder.encode(value.toString(), "UTF-8")); } sb.append("&"); } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } if (sb.length() > 0) { sb.setLength(sb.length() - 1); } return sb.toString(); } @SuppressWarnings("unchecked") public static Map parseUrlLinks(String params) { Map result = new LinkedHashMap<>(); if (params == null || params.isEmpty()) { return result; } for (String param : params.split("&")) { String[] split = param.split("="); String key = split[0]; String value = ""; if (split.length > 1) { value = split[1]; } if (result.containsKey(key)) { Object o = result.get(key); if (o instanceof List) { ((List) o).add(value); } else { List list = new ArrayList<>(); list.add(o); list.add(value); result.put(key, list); } } else { result.put(key, value); } } return result; } /** * 通用接口请求 */ public static HttpResponse request(HttpRequest request) { return request(request, 0); } private static HttpResponse request(HttpRequest request, int retryCount) { HttpRequestBase requestBase = toRequest(request); Map headers = request.getHeaders(); ContentType contentType = null; if (headers != null && !headers.isEmpty()) { for (Map.Entry entry : headers.entrySet()) { String key = entry.getKey(); if (key == null || key.isEmpty()) { continue; } String value = entry.getValue(); if (CONTENT_TYPE.equalsIgnoreCase(key) && value != null) { contentType = ContentType.parse(value); } requestBase.setHeader(key, value); } } // body setBodyEntity(requestBase, contentType, request.getBody()); try { HttpClient client = getHttpClient(request, requestBase); org.apache.http.HttpResponse response; long startTime = System.currentTimeMillis(); response = client.execute(requestBase); HttpResponse httpResponse = new HttpResponse(); httpResponse.setReqTime(System.currentTimeMillis() - startTime); httpResponse.setStatus(response.getStatusLine().getStatusCode()); Header[] allHeaders = response.getAllHeaders(); if (allHeaders != null && allHeaders.length > 0) { httpResponse.setHeaders(new HashMap<>()); for (Header header : allHeaders) { httpResponse.getHeaders().put(header.getName(), header.getValue()); } } HttpEntity entity = response.getEntity(); if (request.responseHandler != null) { httpResponse.setResponse(request.responseHandler.apply(entity)); } else { String charset = null; if (entity.getContentType() != null && entity.getContentType().getValue() != null) { contentType = ContentType.parse(entity.getContentType().getValue()); if (contentType.getCharset() != null) { charset = contentType.getCharset().name(); } } if (charset == null) { charset = CHARSET_DEFAULT; } httpResponse.setResponse(IOUtils.toString(entity.getContent(), charset)); } return httpResponse; } catch (Exception e) { requestBase.abort(); if (request.getMaxRetryCount() > retryCount) { return request(request, retryCount + 1); } else if (e instanceof SocketException && "Connection reset".equals(e.getMessage()) && retryCount == 0 && request.getMaxRetryCount() == 0) { // 遇到 Connection reset 默认重试一次 return request(request, retryCount + 1); } else { throw new RuntimeException("请求异常", e); } } finally { requestBase.releaseConnection(); } } private static void setBodyEntity(HttpRequestBase requestBase, ContentType contentType, Object body) { if (body != null && requestBase instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) requestBase; if (body instanceof HttpEntity) { entityRequest.setEntity((HttpEntity) body); } else if (body instanceof String) { entityRequest.setEntity(getStringEntity((String) body, contentType)); } else if (body instanceof byte[]) { entityRequest.setEntity(new ByteArrayEntity((byte[]) body, contentType)); } else if (body instanceof File) { entityRequest.setEntity(new FileEntity((File) body, contentType)); } else if (body instanceof InputStream) { entityRequest.setEntity(new InputStreamEntity((InputStream) body, contentType)); } else if (ContentType.APPLICATION_JSON.equals(contentType)) { entityRequest.setEntity(getStringEntity(JSON.toJSONString(body), contentType)); } else { entityRequest.setEntity(getStringEntity(body.toString(), contentType)); } } } private static StringEntity getStringEntity(String body, ContentType contentType) { if (contentType != null && contentType.getCharset() != null) { return new StringEntity(body, contentType); } else { return new StringEntity(body, CHARSET_DEFAULT); } } public static HttpRequestBase toRequest(HttpRequest request) { String url = request.getUrl(); String method = request.getMethod(); if (url == null || url.isEmpty()) { throw new RuntimeException("url不能为空"); } if (method == null || method.isEmpty()) { method = "GET"; } switch (method.toUpperCase()) { case "GET": return new HttpGet(url); case "POST": return new HttpPost(url); case "PUT": return new HttpPut(url); case "PATCH": return new HttpPatch(url); case "DELETE": return new HttpDelete(url); default: throw new RuntimeException("不支持的请求方式:" + method); } } private static HttpClient getHttpClient(HttpRequest req, HttpRequestBase request) { RequestConfig.Builder customReqConf = RequestConfig.custom(); if (req.getMaxSocketTimeout() != null) { customReqConf.setSocketTimeout(req.getMaxSocketTimeout()); } else { customReqConf.setSocketTimeout(MAX_SOCKET_TIMEOUT); } customReqConf.setConnectTimeout(MAX_CONNECT_TIMEOUT); customReqConf.setConnectionRequestTimeout(MAX_CONNECT_TIMEOUT); if (req.getRequestConfigConsumer() != null) { req.getRequestConfigConsumer().accept(customReqConf); } request.setConfig(customReqConf.build()); return HttpClients.custom().setConnectionManager(CONNECTION_MANAGER).build(); } private static Registry getDefaultRegistry() { try { // ssl: TLS / TLSv1.2 / TLSv1.3 SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new X509TrustManager[]{new X509TrustManager() { public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { } public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}, new SecureRandom()); return RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", new SSLConnectionSocketFactory(context)).build(); } catch (Exception e) { return RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(); } } public static String toParamLinks(Map params, boolean encode) { List keys = new ArrayList<>(params.keySet()); Collections.sort(keys); StringBuilder sb = new StringBuilder(); try { for (String key : keys) { if (key == null || "".equals(key)) { continue; } Object value = params.get(key); if (value == null || "".equals(value)) { continue; } if (encode) { sb.append(key).append("=").append(URLEncoder.encode(value.toString(), "UTF-8")).append("&"); } else { sb.append(key).append("=").append(value).append("&"); } } } catch (UnsupportedEncodingException e) { throw new RuntimeException("编码失败", e); } if (sb.length() > 0) { sb.setLength(sb.length() - 1); } return sb.toString(); } public static class HttpRequest implements Serializable { private String url; private String method; private Map headers; private Object body; private int maxRetryCount = 0; private Integer maxSocketTimeout; private Consumer requestConfigConsumer; private Function responseHandler; public static HttpRequest build(String url, String method) { HttpRequest request = new HttpRequest(); request.url = url; request.method = method; return request; } public static HttpRequest get(String url) { return build(url, "GET"); } public static HttpRequest postJson(String url) { return build(url, "POST").setContentType(CONTENT_TYPE_JSON); } public static HttpRequest postFormData(String url) { return build(url, "POST").setContentType(CONTENT_TYPE_FORM_DATA); } public String getUrl() { return url; } public HttpRequest setUrl(String url) { this.url = url; return this; } public String getMethod() { return method; } public HttpRequest setMethod(String method) { this.method = method; return this; } public Map getHeaders() { return headers; } public HttpRequest setHeaders(Map headers) { this.headers = headers; return this; } public HttpRequest setContentType(String contentType) { if (this.headers == null) { this.headers = new HashMap<>(); } this.headers.put(CONTENT_TYPE, contentType); return this; } public HttpRequest addHeaders(String key, Object value) { if (headers == null) { headers = new HashMap<>(); } headers.put(key, value != null ? value.toString() : null); return this; } public Object getBody() { return body; } public HttpRequest setBody(Object body) { this.body = body; return this; } public void setMaxRetryCount(int maxRetryCount) { this.maxRetryCount = maxRetryCount; } public int getMaxRetryCount() { return maxRetryCount; } public HttpRequest setMaxSocketTimeout(Integer maxSocketTimeout) { this.maxSocketTimeout = maxSocketTimeout; return this; } public Integer getMaxSocketTimeout() { return maxSocketTimeout; } public Consumer getRequestConfigConsumer() { return requestConfigConsumer; } public void setRequestConfigConsumer(Consumer requestConfigConsumer) { this.requestConfigConsumer = requestConfigConsumer; } public Function getResponseHandler() { return responseHandler; } public HttpRequest setResponseHandler(Function responseHandler) { this.responseHandler = responseHandler; return this; } } public static class HttpResponse implements Serializable { private int status; private long reqTime; private Object response; private Map headers; public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public long getReqTime() { return reqTime; } public void setReqTime(long reqTime) { this.reqTime = reqTime; } public Object getResponse() { return response; } public String getResponseToString() { if (response == null) { return null; } return response instanceof String ? (String) response : String.valueOf(response); } public JSONObject getResponseToJson() { return JSON.parseObject(getResponseToString()); } public void setResponse(Object response) { this.response = response; } public Map getHeaders() { return headers; } public void setHeaders(Map headers) { this.headers = headers; } } }