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

dev.fitko.fitconnect.core.http.DefaultHttpClient Maven / Gradle / Ivy

Go to download

Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and routing

There is a newer version: 2.3.5
Show newest version
package dev.fitko.fitconnect.core.http;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.fitko.fitconnect.api.config.ZBPCertConfig;
import dev.fitko.fitconnect.api.config.http.HttpConfig;
import dev.fitko.fitconnect.api.config.http.ProxyAuth;
import dev.fitko.fitconnect.api.config.http.ProxyConfig;
import dev.fitko.fitconnect.api.config.http.Timeouts;
import dev.fitko.fitconnect.api.exceptions.internal.RestApiException;
import dev.fitko.fitconnect.api.services.http.HttpClient;
import dev.fitko.fitconnect.api.services.http.HttpResponse;
import dev.fitko.fitconnect.core.http.ssl.SSLContextBuilder;
import dev.fitko.fitconnect.core.utils.Preconditions;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class DefaultHttpClient implements HttpClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpClient.class);
    private static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    private final OkHttpClient httpClient;

    public DefaultHttpClient() {
        this(new HttpConfig(), Collections.emptyList());
    }

    public DefaultHttpClient(HttpConfig httpConfig, List interceptors) {
       this(httpConfig, interceptors, null);
    }

    public DefaultHttpClient(HttpConfig httpConfig, List interceptors, ZBPCertConfig zbpCertConfig) {
        var builder = new OkHttpClient.Builder();
        setInterceptors(interceptors, builder);
        setTimeouts(httpConfig.getTimeouts(), builder);
        setProxy(httpConfig.getProxyConfig(), builder);
        if (zbpCertConfig != null) {
            addSSLContext(zbpCertConfig, builder);
        }
        httpClient = builder.build();
    }

    private static void setProxy(final ProxyConfig proxyConfig, final OkHttpClient.Builder builder) {
        if (!proxyConfig.isProxySet()) {
            LOGGER.info("Creating HttpClient without proxy configuration.");
            return;
        }
        final Proxy proxy = proxyConfig.getHttpProxy();
        LOGGER.info("Creating HttpClient with proxy configuration: {}", proxy);
        builder.proxy(proxy);
        if (proxyConfig.hasBasicAuthentication()) {
            LOGGER.info("Creating proxy with basic authentication");
            final Authenticator proxyAuthenticator = getProxyAuthenticator(proxyConfig.getBasicAuth());
            builder.proxyAuthenticator(proxyAuthenticator);
        }
    }

    private static Authenticator getProxyAuthenticator(ProxyAuth auth) {
        return (route, response) -> {
            String credential = Credentials.basic(auth.getUsername(), auth.getPassword());
            return response.request().newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
        };
    }

    private static void setInterceptors(final List interceptors, final OkHttpClient.Builder builder) {
        interceptors.forEach(builder::addInterceptor);
    }

    private static void setTimeouts(final Timeouts timeouts, final OkHttpClient.Builder builder) {
        builder.readTimeout(timeouts.getReadTimeoutInSeconds());
        builder.writeTimeout(timeouts.getWriteTimeoutInSeconds());
        builder.connectTimeout(timeouts.getConnectionTimeoutInSeconds());
    }

    @Override
    public  HttpResponse get(final String url, final Map headers, final Class responseType) {

        final Request request = new Request.Builder()
                .url(url)
                .get()
                .headers(Headers.of(headers))
                .build();

        try (final Response response = httpClient.newCall(request).execute()) {
            return evaluateStatusAndRespond(response, responseType);
        } catch (final RestApiException restApiException) {
            // rethrow to keep status code
            throw restApiException;
        } catch (Exception exception) {
            throw new RestApiException("HTTP GET call to '" + url + "' failed.", exception);
        }
    }

    @Override
    public  HttpResponse post(final String url, final Map headers, final P httpPayload, final Class responseType) {
        try {
            final RequestBody requestBody = createRequestBody(headers, httpPayload);
            final Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .headers(Headers.of(headers))
                    .build();

            try (final Response response = httpClient.newCall(request).execute()) {
                return evaluateStatusAndRespond(response, responseType);
            }

        } catch (final RestApiException restApiException) {
            // rethrow to keep status code
            throw restApiException;
        } catch (Exception exception) {
            throw new RestApiException("HTTP POST call to '" + url + "' failed.", exception);
        }
    }

    @Override
    public  HttpResponse put(final String url, final Map headers, final P httpPayload, final Class responseType) {

        try {
            final RequestBody requestBody = createRequestBody(headers, httpPayload);
            final Request request = new Request.Builder()
                    .url(url)
                    .put(requestBody)
                    .headers(Headers.of(headers))
                    .build();

            try (final Response response = httpClient.newCall(request).execute()) {
                return evaluateStatusAndRespond(response, responseType);
            }

        } catch (final RestApiException restApiException) {
            // rethrow to keep status code
            throw restApiException;
        } catch (Exception exception) {
            throw new RestApiException("HTTP PUT call to '" + url + "' failed.", exception);
        }
    }

    /**
     * The methods {@link ObjectMapper#writeValueAsBytes(Object)} and
     * {@link ObjectMapper#writeValueAsString(Object)} seem to have issues with writing
     * a raw string into a new string, resulting in obsolete quotation marks at begin and end of the actual string.
     * Therefore, this method contains a dedicated logic part for dealing with raw strings.
     * 

* Also, we have to do a little workaround with {@link String#getBytes(Charset)} due to OkHttp enforcing a standard * value for the header "Content-Type" in case of raw strings. See * https://github.com/square/okhttp/issues/2099 * for more information on that issue. */ RequestBody createRequestBody(final Map headers, final T httpPayload) throws JsonProcessingException { if (httpPayload instanceof MultipartBody) { return (MultipartBody) httpPayload; } if (httpPayload instanceof InputStream) { return getRequestBodyWithStreamPayload(headers, (InputStream) httpPayload); } return RequestBody.create(buildPayloadBody(httpPayload), getContentType(headers)); } HttpResponse evaluateStatusAndRespond(final Response response, final Class responseType) throws IOException { if (!response.isSuccessful()) { final String message = response.body() != null ? response.body().string() : "HTTP call failed."; throw new RestApiException(message, response.code()); } final ResponseBody responseBody = response.body(); if (responseBody == null) { throw new RestApiException("Response body is null.", response.code()); } if (responseType.equals(Void.class)) { return new HttpResponse<>(response.code(), null); } if (responseType.equals(String.class)) { return new HttpResponse<>(response.code(), (R) responseBody.string()); } return new HttpResponse<>(response.code(), MAPPER.readValue(responseBody.string(), responseType)); } private void addSSLContext(ZBPCertConfig config, OkHttpClient.Builder clientBuilder) { Preconditions.checkArgumentAndThrow(!config.hasValidConfiguration(), "Required ZBP certificates are not set"); var sslContext = SSLContextBuilder.build(config); clientBuilder.sslSocketFactory(sslContext.getSslSocketFactory(), sslContext.getTrustManager()); } private static RequestBody getRequestBodyWithStreamPayload(final Map headers, final InputStream httpPayload) { return new RequestBody() { @Override public MediaType contentType() { return MediaType.parse(headers.get(HttpHeaders.CONTENT_TYPE)); } @Override public void writeTo(final BufferedSink sink) throws IOException { httpPayload.transferTo(sink.outputStream()); } }; } private static MediaType getContentType(final Map headers) { if (headers.containsKey(HttpHeaders.CONTENT_TYPE)) { return MediaType.parse(headers.get(HttpHeaders.CONTENT_TYPE)); } return MediaType.get(MimeTypes.APPLICATION_JSON); } private static byte[] buildPayloadBody(final T httpPayload) throws JsonProcessingException { if (httpPayload instanceof String) { return ((String) httpPayload).getBytes(StandardCharsets.UTF_8); } return MAPPER.writeValueAsBytes(httpPayload); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy