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

reactivefeign.jetty.client.JettyReactiveHttpClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package reactivefeign.jetty.client;

import com.fasterxml.jackson.core.async_.JsonFactory;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import feign.MethodMetadata;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.reactive.client.ReactiveRequest;
import org.reactivestreams.Publisher;
import reactivefeign.client.ReactiveFeignException;
import reactivefeign.client.ReactiveHttpClient;
import reactivefeign.client.ReactiveHttpRequest;
import reactivefeign.client.ReactiveHttpResponse;
import reactivefeign.client.ReadTimeoutException;
import reactivefeign.utils.SerializedFormData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.TimeUnit;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;
import static org.eclipse.jetty.http.HttpHeader.ACCEPT;
import static org.eclipse.jetty.http.HttpHeader.ACCEPT_ENCODING;
import static reactivefeign.utils.FeignUtils.getBodyActualType;
import static reactivefeign.utils.FeignUtils.returnActualType;
import static reactivefeign.utils.FeignUtils.returnPublisherType;
import static reactivefeign.utils.HttpUtils.APPLICATION_JSON;
import static reactivefeign.utils.HttpUtils.APPLICATION_JSON_UTF_8;
import static reactivefeign.utils.HttpUtils.APPLICATION_OCTET_STREAM;
import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON;
import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON_UTF_8;
import static reactivefeign.utils.HttpUtils.FORM_URL_ENCODED;
import static reactivefeign.utils.HttpUtils.GZIP;
import static reactivefeign.utils.HttpUtils.NEWLINE_SEPARATOR;
import static reactivefeign.utils.HttpUtils.TEXT;
import static reactivefeign.utils.HttpUtils.TEXT_UTF_8;

/**
 * Uses reactive Jetty client to execute http requests
 *
 * @author Sergii Karpenko
 */
public class JettyReactiveHttpClient implements ReactiveHttpClient {

    private final HttpClient httpClient;
    private final Class bodyActualClass;
    private final Class returnPublisherClass;
    private final Class returnActualClass;
    private final JsonFactory jsonFactory;
    private final ObjectWriter bodyWriter;
    private final ObjectReader responseReader;
    private long requestTimeout = -1;
    private boolean tryUseCompression;

    public static JettyReactiveHttpClient jettyClient(
            MethodMetadata methodMetadata,
            HttpClient httpClient,
            JsonFactory jsonFactory, ObjectMapper objectMapper) {

        Class returnPublisherType = returnPublisherType(methodMetadata);
        Type returnActualType = returnActualType(methodMetadata);
        Type bodyActualType = getBodyActualType(methodMetadata.bodyType());
        ObjectWriter bodyWriter = bodyActualType != null
                ? objectMapper.writerFor(objectMapper.constructType(bodyActualType)) : null;
        ObjectReader responseReader = objectMapper.readerFor(objectMapper.constructType(returnActualType));

        return new JettyReactiveHttpClient(httpClient,
                getClass(bodyActualType), returnPublisherType, getClass(returnActualType),
                jsonFactory, bodyWriter, responseReader);
    }

    public JettyReactiveHttpClient(HttpClient httpClient,
                                   Class bodyActualClass, Class returnPublisherClass, Class returnActualClass,
                                   JsonFactory jsonFactory, ObjectWriter bodyWriter, ObjectReader responseReader) {
        this.httpClient = httpClient;
        this.bodyActualClass = bodyActualClass;
        this.returnPublisherClass = returnPublisherClass;
        this.returnActualClass = returnActualClass;
        this.jsonFactory = jsonFactory;
        this.bodyWriter = bodyWriter;
        this.responseReader = responseReader;
    }

    public JettyReactiveHttpClient setRequestTimeout(long timeoutInMillis) {
        this.requestTimeout = timeoutInMillis;
        return this;
    }

    public JettyReactiveHttpClient setTryUseCompression(boolean tryUseCompression) {
        this.tryUseCompression = tryUseCompression;
        return this;
    }

    @Override
    public Mono executeRequest(ReactiveHttpRequest request) {
        Request jettyRequest = httpClient
                .newRequest(request.uri())
                .headers(httpFields -> setUpHeaders(request, httpFields))
                .method(request.method());

        if (requestTimeout > 0) {
            jettyRequest.timeout(requestTimeout, TimeUnit.MILLISECONDS);
        }

        ReactiveRequest.Builder requestBuilder = ReactiveRequest.newBuilder(jettyRequest);
        if (bodyActualClass != null
            || request.body() instanceof SerializedFormData) {
            requestBuilder.content(provideBody(request));
        }

        return Mono.from(requestBuilder.build().response((response, content) -> Mono.just(
                        new JettyReactiveHttpResponse(
                                request,
                                response.getResponse(),
                                content,
                                returnPublisherClass,
                                returnActualClass,
                                jsonFactory,
                                responseReader)
                )))
                .onErrorMap(ex -> {
                    if (ex instanceof java.util.concurrent.TimeoutException) {
                        return new ReadTimeoutException(ex, request);
                    } else {
                        return new ReactiveFeignException(ex, request);
                    }
                });
    }

    protected void setUpHeaders(ReactiveHttpRequest request, HttpFields.Mutable httpHeaders) {
        request.headers().forEach(httpHeaders::put);

        String acceptHeader;
        if (CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class) {
            acceptHeader = TEXT;
        } else if (returnActualClass == ByteBuffer.class || returnActualClass == byte[].class) {
            acceptHeader = APPLICATION_OCTET_STREAM;
        } else if (returnPublisherClass == Mono.class) {
            acceptHeader = APPLICATION_JSON;
        } else {
            acceptHeader = APPLICATION_STREAM_JSON;
        }
        httpHeaders.put(ACCEPT.asString(), singletonList(acceptHeader));

        if (tryUseCompression) {
            httpHeaders.put(ACCEPT_ENCODING.asString(), singletonList(GZIP));
        } else {
            httpHeaders.remove(ACCEPT_ENCODING.asString());
        }
    }

    protected ReactiveRequest.Content provideBody(ReactiveHttpRequest request) {
        Publisher bodyPublisher;
        String contentType;
        if (request.body() instanceof SerializedFormData) {
            bodyPublisher = Mono.just(toByteBufferChunk(((SerializedFormData) request.body()).getFormData(), false));
            contentType = FORM_URL_ENCODED;
        } else if (request.body() instanceof Mono) {
            if (bodyActualClass == ByteBuffer.class) {
                bodyPublisher = ((Mono) request.body()).map(data -> toByteBufferChunk(data, false));
                contentType = APPLICATION_OCTET_STREAM;
            } else if (CharSequence.class.isAssignableFrom(bodyActualClass)) {
                bodyPublisher = Flux.from(request.body()).map(data -> toCharSequenceChunk(data, false));
                contentType = TEXT_UTF_8;
            } else {
                bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false));
                contentType = APPLICATION_JSON_UTF_8;
            }
        } else {
            if (bodyActualClass == ByteBuffer.class) {
                bodyPublisher = Flux.concat(
                        Flux.from(request.body()).map(data -> toByteBufferChunk(data, true)),
                        Flux.just(Content.Chunk.EOF)
                );
                contentType = APPLICATION_OCTET_STREAM;
            } else {
                bodyPublisher = Flux.concat(
                        Flux.from(request.body()).map(data -> toJsonChunk(data, true)),
                        Flux.just(Content.Chunk.EOF)
                );
                contentType = APPLICATION_STREAM_JSON_UTF_8;
            }
        }
        //TODO
        //String originalContentType = request.headers().get(CONTENT_TYPE_HEADER).get(0);

        return ReactiveRequest.Content.fromPublisher(bodyPublisher, contentType);
    }

    protected Content.Chunk toByteBufferChunk(Object data, boolean stream) {
        return Content.Chunk.from((ByteBuffer) data, !stream);
    }

    protected Content.Chunk toCharSequenceChunk(Object data, boolean stream) {
        CharBuffer charBuffer = CharBuffer.wrap((CharSequence) data);
        ByteBuffer byteBuffer = UTF_8.encode(charBuffer);
        return Content.Chunk.from(byteBuffer, !stream);
    }

    protected Content.Chunk toJsonChunk(Object data, boolean stream) {
        try {
            ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder();
            bodyWriter.writeValue(byteArrayBuilder, data);
            if (stream) {
                byteArrayBuilder.write(NEWLINE_SEPARATOR);
            }
            ByteBuffer buffer = ByteBuffer.wrap(byteArrayBuilder.toByteArray());
            return Content.Chunk.from(buffer, !stream);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static Class getClass(Type type) {
        return (Class) (type instanceof ParameterizedType pt ? pt.getRawType() : type);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy