HttpClientUtils
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
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.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.entity.ContentType;
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.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.TextUtils;
import javax.net.ssl.SSLContext;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringReader;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.Charset;
import java.util.*;
public class HttpClientUtils {
private static final int CONNECT_TIMEOUT = 8000;
private static final int SOCKET_TIMEOUT = 8000;
private static final int MAX_TOTAL = 512;
private static final int MAX_PER_ROUTE = 32;
private static final String CONTENT_TYPE = "Content-Type";
private static final String USER_AGENT = "User-Agent";
private static final String DEFAULT_CHARSET = "UTF-8";
private static final String TEXT$ = "text";
private static final String JSON$ = "json";
private static final String XML$ = "xml";
private static final String EMPTY = "";
// region HttpRequest part
private static HttpUriRequest createRequestBase(String url, String method) {
HttpUriRequest request = null;
switch (Objects.nonNull(method) ? method.toUpperCase() : EMPTY) {
case HttpGet.METHOD_NAME:
request = new HttpGet(url);
break;
case HttpPost.METHOD_NAME:
request = new HttpPost(url);
break;
case HttpPut.METHOD_NAME:
request = new HttpPut(url);
break;
case HttpPatch.METHOD_NAME:
request = new HttpPatch(url);
break;
case HttpDelete.METHOD_NAME:
request = new HttpDelete(url);
break;
case HttpHead.METHOD_NAME:
request = new HttpHead(url);
break;
case HttpOptions.METHOD_NAME:
request = new HttpOptions(url);
break;
}
return request;
}
private static void createRequestHeaders(HttpUriRequest request, HttpHeaders requestHead) {
if (Objects.nonNull(request)) {
if (Objects.nonNull(requestHead) && !requestHead.isEmpty()) {
requestHead.forEach((k, v) -> request.setHeader(k, requestHead.getFirst(k)));
} else {
request.setHeader(CONTENT_TYPE, "application/json");
}
request.setHeader(USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0");
}
}
private static void createRequestBody(HttpUriRequest request, HttpEntity requestBody) {
if (Objects.nonNull(request) && (request instanceof HttpEntityEnclosingRequestBase)) {
((HttpEntityEnclosingRequestBase) request).setEntity(requestBody);
}
}
// endregion HttpRequest
// region HttpResponse part
@Data
public static class HttpStatus {
int value;
String reasonPhrase;
}
public static class HttpHeaders extends HashMap<String, List<String>> {
public String getFirst(String headerName) {
List<String> headerValues = super.get(headerName);
return headerValues != null ? headerValues.get(0) : null;
}
public void add(String headerName, String headerValue) {
super.computeIfAbsent(headerName, (k) -> new LinkedList<>()).add(headerValue);
}
public void addAll(String headerName, List<? extends String> headerValues) {
super.computeIfAbsent(headerName, (k) -> new LinkedList<>()).addAll(headerValues);
}
public void addAll(Map<String, List<String>> values) {
values.forEach(this::addAll);
}
public void set(String headerName, String headerValue) {
List<String> headerValues = new LinkedList<>();
headerValues.add(headerValue);
super.put(headerName, headerValues);
}
public void setAll(Map<String, String> values) {
values.forEach(this::set);
}
public Map<String, String> toSingleValueMap() {
LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<>(super.size());
super.forEach((key, valueList) -> singleValueMap.put(key, valueList.get(0)));
return singleValueMap;
}
public ContentType getContentType() {
String value = this.getFirst(CONTENT_TYPE);
return TextUtils.isBlank(value) ? null : ContentType.parse(value);
}
}
@Data
public static class ResponseEntity {
HttpStatus status;
HttpHeaders headers;
Object body;
public boolean hasBody() {
return null != this.body;
}
String getMimeType() {
if (Objects.nonNull(headers)) {
ContentType contentType = headers.getContentType();
if (Objects.nonNull(contentType)) {
String mimeType = contentType.getMimeType();
if (Objects.nonNull(mimeType)) {
return mimeType;
}
}
}
return EMPTY;
}
Charset getCharset() {
if (Objects.nonNull(headers)) {
ContentType contentType = headers.getContentType();
if (Objects.nonNull(contentType)) {
Charset charset = contentType.getCharset();
if (Objects.nonNull(charset)) {
return charset;
}
}
}
return Charset.forName(DEFAULT_CHARSET);
}
}
private static void createResponseHeaders(ResponseEntity response, HeaderIterator iterator) {
if (Objects.nonNull(response) && Objects.nonNull(iterator)) {
HttpHeaders headers = new HttpHeaders();
while (iterator.hasNext()) {
final Header header = iterator.nextHeader();
headers.add(header.getName(), header.getValue());
}
response.setHeaders(headers);
}
}
private static void createResponseStatus(ResponseEntity response, StatusLine statusLine) {
if (Objects.nonNull(response) && Objects.nonNull(statusLine)) {
HttpStatus status = new HttpStatus();
status.setValue(statusLine.getStatusCode());
status.setReasonPhrase(statusLine.getReasonPhrase());
response.setStatus(status);
}
}
// endregion HttpResponse
// region MessageConverter part
@SuppressWarnings("unchecked")
@SneakyThrows
private static <T> T parseObjectFromXMLString(String xml, ParameterizedType type) {
if (!TextUtils.isBlank(xml) && Objects.nonNull(type)) {
Class<?>[] classes = ParameterizedTypeUtils.getClasses(type);
if (classes.length == 0 || (classes.length == 1 && String.class == classes[0])) {
return (T) xml;
}
JAXBContext context = JAXBContext.newInstance(classes);
Unmarshaller um = context.createUnmarshaller();
return (T) um.unmarshal(new StringReader(xml));
}
return null;
}
private static <T> T parseObjectFromJSONString(String json, ParameterizedType type) {
if (ParameterizedTypeUtils.hasActualTypeArguments(type)) {
return JSON.parseObject(json, type);
} else {
Class<T> clazz = ParameterizedTypeUtils.getClass(type);
return JSON.parseObject(json, clazz);
}
}
private static String toJSONString(Object obj) {
return JSON.toJSONString(obj);
}
// endregion MessageConverter
@SneakyThrows
private static CloseableHttpClient getCustomHttpClient() {
RequestConfig reqCfg = RequestConfig.custom()
.setSocketTimeout(SOCKET_TIMEOUT)
.setConnectTimeout(CONNECT_TIMEOUT)
.build();
SocketConfig sockCfg = SocketConfig.custom()
.setTcpNoDelay(true)
.setSoTimeout(SOCKET_TIMEOUT)
.build();
SSLContext sslCtx = SSLContexts.custom()
.loadTrustMaterial(TrustAllStrategy.INSTANCE)
.build();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", new SSLConnectionSocketFactory(sslCtx, NoopHostnameVerifier.INSTANCE))
.build();
PoolingHttpClientConnectionManager manager
= new PoolingHttpClientConnectionManager(registry);
manager.setMaxTotal(MAX_TOTAL);
manager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
HttpClientBuilder builder = HttpClients.custom();
builder.setDefaultRequestConfig(reqCfg);
builder.setDefaultSocketConfig(sockCfg);
builder.setConnectionManager(manager);
builder.setRetryHandler(new DefaultHttpRequestRetryHandler(1, true));
return builder.build();
}
private static ResponseEntity exchange(String url,
String method,
HttpEntity requestBody,
HttpHeaders requestHead) {
ResponseEntity ret = new ResponseEntity();
HttpUriRequest request = createRequestBase(url, method);
if (Objects.nonNull(request)) {
createRequestHeaders(request, requestHead);
createRequestBody(request, requestBody);
try (CloseableHttpClient httpClient = getCustomHttpClient();
CloseableHttpResponse response = httpClient.execute(request)) {
final StatusLine statusLine = response.getStatusLine();
createResponseStatus(ret, statusLine);
final HeaderIterator headerIterator = response.headerIterator();
createResponseHeaders(ret, headerIterator);
final HttpEntity responseEntity = response.getEntity();
if (Objects.nonNull(responseEntity)) {
ret.setBody(EntityUtils.toByteArray(responseEntity));
}
} catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
return ret;
}
@SuppressWarnings("unchecked")
public static <T> T fetch(String url,
String method,
HttpEntity body,
HttpHeaders headers,
ParameterizedType parameterizedType) {
ResponseEntity responseEntity = exchange(url, method, body, headers);
if (!responseEntity.hasBody()) {
return null;
}
Class<T> clazz = ParameterizedTypeUtils.getClass(parameterizedType);
final Object responseEntityBody = responseEntity.getBody();
if (Objects.equals(clazz, responseEntityBody.getClass())) {
return (T) responseEntityBody;
}
final String mime = responseEntity.getMimeType().toLowerCase();
boolean isJson = mime.contains(JSON$), isXml = mime.contains(XML$), isText = mime.contains(TEXT$);
String data;
if (isJson || isXml || isText) {
data = new String((byte[]) responseEntityBody, responseEntity.getCharset());
if (isXml && Objects.nonNull(clazz.getDeclaredAnnotation(XmlRootElement.class))) {
return parseObjectFromXMLString(data, parameterizedType);
}
return parseObjectFromJSONString(data, parameterizedType);
}
data = toJSONString(responseEntityBody);
return parseObjectFromJSONString(data, parameterizedType);
}
public static <T> T fetch(String url,
String method,
HttpEntity body,
HttpHeaders headers,
TypeReference<T> responseType) {
ParameterizedType parameterizedType = ParameterizedTypeUtils.make(responseType);
return fetch(url, method, body, headers, parameterizedType);
}
public static <T> T fetch(String url,
String method,
HttpEntity body,
HttpHeaders headers,
Class<T> clazz) {
ParameterizedType parameterizedType = ParameterizedTypeUtils.make(clazz);
return fetch(url, method, body, headers, parameterizedType);
}
/*
public static void main(String[] args) throws Exception {
// List<Header> headers = new ArrayList<>();
// headers.add(new BasicHeader("Content-Type", "multipart/form-data"));
// MultipartEntityBuilder form = MultipartEntityBuilder.create();
// form.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
// form.addTextBody("data", "");
// form.addBinaryBody("file", new File(""));
// form.addBinaryBody("byte", new ByteArrayInputStream(new byte[0]));
// HttpEntity body = form.build();
// fetch("url", "POST", body, headers, JSONObject.class);
// */
}
ParameterizedTypeUtils
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
public class ParameterizedTypeUtils {
public static ParameterizedType make(TypeReference<?> typeReference) {
return (ParameterizedType) typeReference.getType();
}
public static ParameterizedType make(Type rawType) {
return make(rawType, null, null);
}
public static ParameterizedType make(Type rawType, Type[] actualTypeArguments) {
return make(rawType, actualTypeArguments, null);
}
public static ParameterizedType make(Type rawType, Type[] actualTypeArguments, Type ownerType) {
// sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(Class<?>, Type[], Type);
return new ParameterizedTypeImpl(actualTypeArguments, ownerType, rawType);
}
public static Type getRawType(ParameterizedType parameterizedType) {
return parameterizedType.getRawType();
}
public static Type getOwnerType(ParameterizedType parameterizedType) {
return parameterizedType.getOwnerType();
}
public static Type[] getActualTypeArguments(ParameterizedType parameterizedType) {
Type[] argTypes = parameterizedType.getActualTypeArguments();
if (Objects.isNull(argTypes)) {
argTypes = new Type[0];
}
return argTypes;
}
public static boolean hasRawType(ParameterizedType parameterizedType) {
return Objects.nonNull(getRawType(parameterizedType));
}
public static boolean hasOwnerType(ParameterizedType parameterizedType) {
return Objects.nonNull(getOwnerType(parameterizedType));
}
public static boolean hasActualTypeArguments(ParameterizedType parameterizedType) {
return getActualTypeArguments(parameterizedType).length > 0;
}
@SuppressWarnings("unchecked")
public static <T> Class<T> getClass(ParameterizedType parameterizedType) {
return (Class<T>) parameterizedType.getRawType();
}
public static Class<?>[] getClasses(ParameterizedType parameterizedType) {
Type[] typeArgs = getActualTypeArguments(parameterizedType);
Class<?>[] classes = new Class[typeArgs.length + 1];
classes[0] = getClass(parameterizedType);
Type item;
for (int i = 0, cnt = typeArgs.length; i < cnt; i++) {
item = typeArgs[i];
classes[i + 1] = (item instanceof ParameterizedType
? getClass((ParameterizedType) item)
: (Class<?>) item);
}
return classes;
}
}