com.feingto.cloud.kit.http.OKHttpClient Maven / Gradle / Ivy
package com.feingto.cloud.kit.http;
import com.fasterxml.jackson.databind.JsonNode;
import com.feingto.cloud.kit.json.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.collections.MapUtils;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static okhttp3.internal.http.HttpMethod.permitsRequestBody;
/**
* OKHttp 客户端
*
* @author longfei
*/
@Slf4j
public class OKHttpClient {
private static volatile OKHttpClient instance;
protected OkHttpClient client;
public OKHttpClient(ConnectProperties properties) {
this.init(properties);
}
public static OKHttpClient getInstance() {
if (Objects.isNull(instance)) {
synchronized (OKHttpClient.class) {
if (Objects.isNull(instance)) {
instance = new OKHttpClient(new ConnectProperties());
}
}
}
return instance;
}
public void init(ConnectProperties properties) {
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(properties.getMaxTotalConnections());
dispatcher.setMaxRequestsPerHost(properties.getMaxConnectionsPerHost());
this.client = new OkHttpClient.Builder()
.connectTimeout(properties.getConnectTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(properties.getReadTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(properties.getWriteTimeout(), TimeUnit.MILLISECONDS)
.followRedirects(properties.isFollowRedirects())
.dispatcher(dispatcher)
.build();
}
private Call call(ClientRequest request, ConnectProperties properties) {
return Objects.nonNull(properties) ?
client.newBuilder()
.connectTimeout(properties.getConnectTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(properties.getReadTimeout(), TimeUnit.MILLISECONDS)
.followRedirects(properties.isFollowRedirects())
.build()
.newCall(buildRequest(request)) :
client.newCall(buildRequest(request));
}
/**
* 同步调用
* Leaves the stream open when done.
*
* @param request ClientRequest
* @return ClientResponse
*/
public ClientResponse invoke(ClientRequest request) {
return this.invoke(request, null);
}
/**
* 同步调用
* Leaves the stream open when done.
*
* @param request ClientRequest
* @param properties ConnectProperties
* @return ClientResponse
*/
public ClientResponse invoke(ClientRequest request, ConnectProperties properties) {
return this.invoke(request, properties, getMaxAutoRetries(properties));
}
/**
* 同步调用
* Leaves the stream open when done.
*
* @param request ClientRequest
* @param properties ConnectProperties
* @param maxAutoRetries 重试次数
* @return ClientResponse
*/
public ClientResponse invoke(ClientRequest request, ConnectProperties properties, int maxAutoRetries) {
try (Response response = this.call(request, properties).execute()) {
ClientResponse clientResponse = new ClientResponse()
.setStatusCode(response.code())
.setHeaders(toHttpHeaders(response.headers()));
ResponseBody body = response.body();
if (response.isSuccessful()) {
return clientResponse
.setSuccessful(true)
.setBody(Objects.nonNull(body)
? new ByteArrayInputStream(body.bytes())
: StreamUtils.emptyInput());
} else {
byte[] bytes = Objects.nonNull(body) ? body.bytes() : new byte[0];
JsonNode node = JSON.read(bytes);
String message = node.has("message") ? node.get("message").asText() : "";
return clientResponse
.setSuccessful(false)
.setMessage(message)
.setBody(new ByteArrayInputStream(bytes));
}
} catch (Exception e) {
if (maxAutoRetries > 0) {
log.error("The request has been aborted and is being retried");
return this.invoke(request, properties, maxAutoRetries - 1);
}
return Optional.of(e)
.filter(ex -> ex instanceof SocketTimeoutException || ex instanceof ConnectException)
.map(ex -> ClientResponse.requestTimeout())
.orElse(ClientResponse.error("The request has been aborted: " + e.getMessage()));
}
}
/**
* 异步调用
*
* @param request ClientRequest
* @return ClientResponse
*/
public ClientResponse invokeAsync(ClientRequest request) {
return this.invokeAsync(request, null);
}
/**
* 异步调用
*
* @param request ClientRequest
* @param properties ConnectProperties
* @return ClientResponse
*/
public ClientResponse invokeAsync(ClientRequest request, ConnectProperties properties) {
return this.invokeAsync(request, properties, getMaxAutoRetries(properties));
}
/**
* 异步调用
*
* @param request ClientRequest
* @param properties ConnectProperties
* @param maxAutoRetries 重试次数
* @return ClientResponse
*/
public ClientResponse invokeAsync(ClientRequest request, ConnectProperties properties, int maxAutoRetries) {
try {
return this.executeAsync(request, properties).get();
} catch (InterruptedException | ExecutionException e) {
if (maxAutoRetries > 0) {
log.error("The request has been aborted and is being retried");
return this.invokeAsync(request, properties, maxAutoRetries - 1);
}
return Optional.of(e)
.filter(ex -> ex instanceof SocketTimeoutException || ex instanceof ConnectException)
.map(ex -> ClientResponse.requestTimeout())
.orElse(ClientResponse.error("The request has been aborted: " + e.getMessage()));
}
}
/**
* 异步调用
*
* @param requests List
* @return List
*/
public List invokeAsync(List requests) {
return invokeAsync(requests, null);
}
/**
* 异步调用
*
* @param requests List
* @param properties ConnectProperties
* @return List
*/
@SuppressWarnings("rawtypes")
public List invokeAsync(List requests, ConnectProperties properties) {
List results = new ArrayList<>();
AtomicInteger latch = new AtomicInteger(requests.size());
AtomicBoolean hasDisposition = new AtomicBoolean(false);
CompletableFuture[] tasks = requests.stream()
.filter(request -> !hasDisposition.get())
.map(request -> CompletableFuture
.supplyAsync(() -> this.executeAsync(request, properties))
.thenApply(future -> {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
return ClientResponse.error(e.getMessage());
}
})
.thenAccept(response -> {
hasDisposition.set(response.getHeaders().containsKey(HttpHeaders.CONTENT_DISPOSITION));
results.add(response);
})
.thenRun(latch::getAndDecrement))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(tasks).join();
return results;
}
/**
* 异步执行
*
* @param request ClientRequest
* @return ListenableFuture
*/
public ListenableFuture executeAsync(ClientRequest request, ConnectProperties properties) {
return new OkHttpListenableFuture(this.call(request, properties));
}
/**
* 异步回调
*/
private static class OkHttpListenableFuture extends SettableListenableFuture {
private final Call call;
public OkHttpListenableFuture(Call call) {
this.call = call;
this.call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
set(new ClientResponse()
.setStatusCode(response.code())
.setBody(Objects.nonNull(body)
? new ByteArrayInputStream(body.bytes())
: StreamUtils.emptyInput())
.setHeaders(toHttpHeaders(response.headers()))
.setSuccessful(response.isSuccessful()));
}
}
@Override
public void onFailure(Call call, IOException ex) {
setException(ex);
}
});
}
@Override
protected void interruptTask() {
this.call.cancel();
}
}
@SneakyThrows
public static Request buildRequest(ClientRequest request) {
HttpMethod method = request.method();
Map headers = request.headers();
// 剔除Accept-Encoding头,OkHttp自动添加压缩头并解压缩(否则需要自行解压缩)
headers.remove(HttpHeaders.ACCEPT_ENCODING.toLowerCase());
String url = request.path();
if (Objects.nonNull(request.body())) {
RequestBody requestBody = null;
if (permitsRequestBody(method.name())) {
MediaType mediaType = null;
if (Objects.nonNull(headers.get("Content-Type"))) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, request.body());
}
return new Request.Builder()
.headers(Headers.of(headers).newBuilder().build())
.url(url)
.method(method.name(), requestBody).build();
} else {
Request.Builder builder = new Request.Builder()
.headers(Headers.of(headers).newBuilder().build());
Map params = request.queries();
if (method.equals(HttpMethod.GET)) {
builder = builder.url(buildUri(url, params).toURL());
} else {
FormBody.Builder formBuilder = new FormBody.Builder();
params.keySet().stream()
.filter(key -> StringUtils.hasLength(params.get(key)))
.forEach(key -> formBuilder.add(key, params.get(key)));
builder = builder.url(url).method(method.name(), formBuilder.build());
}
return builder.build();
}
}
@SneakyThrows
public static URI buildUri(String url, Map params) {
URIBuilder uri = new URIBuilder(url);
if (MapUtils.isNotEmpty(params)) {
params.forEach(uri::addParameter);
}
return uri.build();
}
public static HttpHeaders toHttpHeaders(Headers headers) {
HttpHeaders httpHeaders = new HttpHeaders();
headers.names().forEach(name -> httpHeaders.add(name, headers.get(name)));
return httpHeaders;
}
public static int getMaxAutoRetries(ConnectProperties properties) {
return Objects.nonNull(properties) ? properties.getMaxAutoRetries() : ConnectProperties.DEFAULT_MAX_AUTO_RETRIES;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy