All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feingto.cloud.kit.http.OKHttpClient Maven / Gradle / Ivy

There is a newer version: 2.5.2.RELEASE
Show newest version
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 - 2024 Weber Informatics LLC | Privacy Policy