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

io.github.dbstarll.utils.net.api.ApiAsyncClient Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
package io.github.dbstarll.utils.net.api;

import io.github.dbstarll.utils.net.api.index.Index;
import org.apache.commons.io.IOUtils;
import org.apache.hc.client5.http.async.HttpAsyncClient;
import org.apache.hc.core5.concurrent.CallbackContribution;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
import org.apache.hc.core5.http.protocol.HttpContext;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;

import static org.apache.commons.lang3.Validate.notNull;

public abstract class ApiAsyncClient extends AbstractApiClient {
    private static final String RESPONSE_CONSUMER_IS_NULL_EX_MESSAGE = "responseConsumer is null";

    private Charset responseCharset = StandardCharsets.UTF_8;

    protected ApiAsyncClient(final HttpAsyncClient httpClient, final boolean alwaysProcessEntity) {
        super(httpClient, alwaysProcessEntity);
        setResponseHandlerFactory(new BasicIndexResponseHandlerFactory(getResponseHandler(String.class)));
    }

    protected final void setResponseCharset(final Charset responseCharset) {
        this.responseCharset = notNull(responseCharset, "responseCharset is null");
    }

    private AsyncRequestProducer buildRequestProducer(final ClassicHttpRequest request) throws IOException {
        final HttpEntity entity = request.getEntity();
        if (entity != null) {
            final byte[] data = IOUtils.readFully(entity.getContent(), (int) entity.getContentLength());
            final ContentType contentType = ContentType.parse(entity.getContentType());
            return new BasicRequestProducer(request, AsyncEntityProducers.create(data, contentType));
        } else {
            return new BasicRequestProducer(request, null);
        }
    }

    /**
     * Triggered to signal receipt of a response message head.
     *
     * @param request       the request
     * @param response      the response message head.
     * @param entityDetails the response entity details or {@code null} if the response
     *                      does not enclose an entity.
     * @throws HttpException HttpException
     * @throws IOException   IOException
     */
    @SuppressWarnings("RedundantThrows")
    protected void consumeResponse(final ClassicHttpRequest request, final HttpResponse response,
                                   final EntityDetails entityDetails) throws HttpException, IOException {
        final String traceEntity = format(entityDetails);
        logger.trace("response: [{}]@{} with {}:{}", request, request.hashCode(), response.getCode(), traceEntity);
    }

    /**
     * Triggered to pass incoming data to the data consumer.
     *
     * @param request       the request
     * @param response      the response message head.
     * @param entityDetails the response entity details or {@code null} if the response
     *                      does not enclose an entity.
     * @param src           data source.
     */
    protected void consume(final ClassicHttpRequest request, final HttpResponse response,
                           final EntityDetails entityDetails, final ByteBuffer src) {
        logger.trace("consume: [{}]@{} with {}:[{}]:{} bytes", request, request.hashCode(), response.getCode(),
                entityDetails.getContentType(), src.remaining());
    }

    /**
     * Triggered to pass incoming data packet to the data consumer.
     *
     * @param request     the request
     * @param contentType ContentType
     * @param endOfStream 数据流是否结束
     * @param result      the data packet.
     * @param          type of result
     * @return result
     */
    protected  T stream(final ClassicHttpRequest request, final ContentType contentType, final boolean endOfStream,
                           final T result) {
        logger.trace("stream: [{}]@{} with [{}]:{}:[{}]{}", request, request.hashCode(), contentType,
                result.getClass().getName(), result, endOfStream ? "END" : "");
        return result;
    }

    /**
     * 在获取请求结果之后,对结果进行包装.
     *
     * @param request the request to execute
     * @param result  请求结果
     * @param      请求结果类型
     * @return 请求结果
     */
    protected  T completed(final ClassicHttpRequest request, final T result) {
        if (result != null) {
            logger.trace("completed: [{}]@{} with {}:[{}]", request, request.hashCode(),
                    result.getClass().getName(), result);
        } else {
            logger.trace("completed: [{}]@{} with null", request, request.hashCode());
        }
        return result;
    }

    protected final  Future execute(final ClassicHttpRequest request,
                                          final AsyncResponseConsumer responseConsumer,
                                          final FutureCallback callback) throws IOException {
        notNull(responseConsumer, RESPONSE_CONSUMER_IS_NULL_EX_MESSAGE);

        traceRequest(request);

        return httpClient.execute(buildRequestProducer(request), new AsyncResponseConsumerWrapper(responseConsumer) {
            private final AtomicReference refHttpResponse = new AtomicReference<>();
            private final AtomicReference refEntityDetails = new AtomicReference<>();

            @Override
            public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails,
                                        final HttpContext context, final FutureCallback resultCallback)
                    throws HttpException, IOException {
                this.refHttpResponse.set(response);
                this.refEntityDetails.set(entityDetails);
                ApiAsyncClient.this.consumeResponse(request, response, entityDetails);
                super.consumeResponse(response, entityDetails, context, resultCallback);
            }

            @Override
            public void consume(final ByteBuffer src) throws IOException {
                final int position = src.position();
                try {
                    ApiAsyncClient.this.consume(request, refHttpResponse.get(), refEntityDetails.get(), src);
                } finally {
                    src.position(position);
                }
                super.consume(src);
            }

            @Override
            public void releaseResources() {
                this.refHttpResponse.set(null);
                this.refEntityDetails.set(null);
                super.releaseResources();
            }
        }, null, null, new CallbackContribution(callback) {
            @Override
            public void completed(final T result) {
                final T finalResult = ApiAsyncClient.this.completed(request, result);
                if (callback != null) {
                    callback.completed(finalResult);
                }
            }
        });
    }

    protected final  Future execute(final ClassicHttpRequest request,
                                          final HttpClientResponseHandler responseHandler,
                                          final FutureCallback callback) throws IOException {
        notNull(responseHandler, "responseHandler is null");
        return execute(request, ResponseHandlerResponseConsumer.create(responseHandler, responseCharset), callback);
    }

    /**
     * 根据请求结果类来获得相应的请求结果.
     *
     * @param request       the request to execute
     * @param responseClass 请求结果类
     * @param callback      FutureCallback
     * @param            请求结果类型
     * @return 请求结果
     * @throws IOException IOException
     */
    protected  Future execute(final ClassicHttpRequest request,
                                    final Class responseClass,
                                    final FutureCallback callback) throws IOException {
        notNull(responseClass, "responseClass is null");
        return execute(request, getResponseHandler(responseClass), callback);
    }

    protected final  Future execute(final ClassicHttpRequest request,
                                             final HttpClientResponseHandler> responseHandler,
                                             final StreamFutureCallback callback) throws IOException {
        notNull(responseHandler, "responseHandler is null");
        notNull(callback, "callback is null");
        return execute(request, StreamResponseHandlerResponseConsumer.create(responseHandler, responseCharset,
                (contentType, endOfStream, result) -> callback.stream(contentType, endOfStream,
                        ApiAsyncClient.this.stream(request, contentType, endOfStream, result))), callback);
    }

    /**
     * 根据请求结果类来获得相应的流式请求结果.
     *
     * @param request       the request to execute
     * @param responseClass 请求结果类
     * @param callback      StreamFutureCallback
     * @param            请求结果类型
     * @return 请求结果
     * @throws IOException IOException
     */
    protected  Future execute(final ClassicHttpRequest request, final Class responseClass,
                                       final StreamFutureCallback callback) throws IOException {
        notNull(responseClass, "responseClass is null");
        notNull(callback, "callback is null");
        final Class> streamResponseClass = getStreamResponseClass(responseClass);
        notNull(streamResponseClass, "streamResponseClass is null");
        return execute(request, getResponseHandler(streamResponseClass), callback);
    }

    @SuppressWarnings("unchecked")
    protected final  Class> getStreamResponseClass(final Class responseClass) {
        for (Class c : responseClassIterator()) {
            if (Index.class.isAssignableFrom(c)
                    && responseClass == ((ParameterizedType) c.getGenericSuperclass()).getActualTypeArguments()[0]) {
                return (Class>) c;
            }
        }
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy