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

com.gocardless.http.HttpClient Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
package com.gocardless.http;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import com.github.rholder.retry.*;
import com.gocardless.GoCardlessException;
import com.gocardless.errors.GoCardlessInternalException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import okhttp3.*;

/**
 * An HTTP client that can execute {@link ApiRequest}s.
 *
 * Users of this library should not need to access this class directly.
 */
public class HttpClient {
    /**
     * The maximum number of times that a request can be retried.
     */
    public static final int MAX_RETRIES = 3;
    /**
     * The amount of time to wait before retrying a failed request in milli seconds
     */
    public static final long WAIT_BETWEEN_RETRIES_IN_MILLI_SECONDS = 500;
    /**
     * See http://tools.ietf.org/html/rfc7230#section-3.2.6.
     */
    private static final String DISALLOWED_USER_AGENT_CHARACTERS =
            "[^\\w!#$%&'\\*\\+\\-\\.\\^`\\|~]";
    private static final String USER_AGENT =
            String.format("gocardless-pro-java/5.23.0 java/%s %s/%s %s/%s",
                    cleanUserAgentToken(System.getProperty("java.vm.specification.version")),
                    cleanUserAgentToken(System.getProperty("java.vm.name")),
                    cleanUserAgentToken(System.getProperty("java.version")),
                    cleanUserAgentToken(System.getProperty("os.name")),
                    cleanUserAgentToken(System.getProperty("os.version")));
    private static final RequestBody EMPTY_BODY = RequestBody.create(null, new byte[0]);
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json");
    private static final Map HEADERS;
    static {
        ImmutableMap.Builder builder = ImmutableMap.builder();
        builder.put("GoCardless-Version", "2015-07-06");
        builder.put("Accept", "application/json");
        builder.put("GoCardless-Client-Library", "gocardless-pro-java");
        builder.put("GoCardless-Client-Version", "5.23.0");
        HEADERS = builder.build();
    }
    private final OkHttpClient rawClient;
    private final UrlFormatter urlFormatter;
    private final ResponseParser responseParser;
    private final RequestWriter requestWriter;
    private final String credentials;
    private final boolean errorOnIdempotencyConflict;
    private final int maxNoOfRetries;
    private final long waitBetweenRetriesInMilliSeconds;

    /**
     * Constructor. Users of this library should not need to access this class directly - you should
     * instantiate a GoCardlessClient and its underlying HttpClient using
     * GoCardlessClient.newBuilder().
     *
     * @param accessToken the access token.
     * @param baseUrl base URI to make requests against.
     * @param rawClient the OkHttpClient instance to use to make requests (which will be configured
     *        to log requests with LoggingInterceptor).
     */
    public HttpClient(String accessToken, String baseUrl, OkHttpClient rawClient,
            boolean errorOnIdempotencyConflict, int maxNoOfRetries,
            long waitBetweenRetriesInMilliSeconds) {
        this.rawClient = rawClient;
        this.urlFormatter = new UrlFormatter(baseUrl);
        Gson gson = GsonFactory.build();
        this.responseParser = new ResponseParser(gson);
        this.requestWriter = new RequestWriter(gson);
        this.credentials = String.format("Bearer %s", accessToken);
        this.errorOnIdempotencyConflict = errorOnIdempotencyConflict;
        this.maxNoOfRetries = maxNoOfRetries;
        this.waitBetweenRetriesInMilliSeconds = waitBetweenRetriesInMilliSeconds;
    }

    public boolean isErrorOnIdempotencyConflict() {
        return this.errorOnIdempotencyConflict;
    }

    @VisibleForTesting
    public int getMaxNoOfRetries() {
        return this.maxNoOfRetries;
    }

     T execute(ApiRequest apiRequest) {
        Request request = buildRequest(apiRequest);
        Response response = execute(request);
        return parseResponseBody(apiRequest, response);
    }

     ApiResponse executeWrapped(ApiRequest apiRequest) {
        Request request = buildRequest(apiRequest);
        Response response = execute(request);
        T resource = parseResponseBody(apiRequest, response);
        return new ApiResponse<>(resource, response.code(), response.headers().toMultimap());
    }

     T executeWithRetries(final ApiRequest apiRequest) {
        Retryer retrier = RetryerBuilder.newBuilder()
                .retryIfExceptionOfType(GoCardlessNetworkException.class)
                .retryIfExceptionOfType(GoCardlessInternalException.class)
                .withWaitStrategy(WaitStrategies.fixedWait(this.waitBetweenRetriesInMilliSeconds,
                        MILLISECONDS))
                .withStopStrategy(StopStrategies.stopAfterAttempt(this.maxNoOfRetries)).build();
        Callable executeOnce = new Callable() {
            @Override
            public T call() throws Exception {
                return execute(apiRequest);
            }
        };
        try {
            return retrier.call(executeOnce);
        } catch (ExecutionException | RetryException e) {
            Throwable cause = e.getCause();
            throw Throwables.propagate(cause);
        }
    }

    private  Request buildRequest(ApiRequest apiRequest) {
        HttpUrl url = apiRequest.getUrl(urlFormatter);
        Request.Builder request =
                new Request.Builder().url(url).headers(Headers.of(apiRequest.getHeaders()))
                        .header("Authorization", credentials).header("User-Agent", USER_AGENT)
                        .method(apiRequest.getMethod(), getBody(apiRequest));
        for (Map.Entry entry : HEADERS.entrySet()) {
            request = request.header(entry.getKey(), entry.getValue());
        }
        return request.build();
    }

    private  RequestBody getBody(ApiRequest request) {
        if (!request.hasBody()) {
            if (request.getMethod().equals("GET")) {
                return null;
            } else {
                return EMPTY_BODY;
            }
        }
        String json = requestWriter.write(request, request.getRequestEnvelope());
        return RequestBody.create(MEDIA_TYPE, json);
    }

    private Response execute(Request request) {
        Response response;
        try {
            response = rawClient.newCall(request).execute();
        } catch (IOException e) {
            throw new GoCardlessNetworkException("Failed to execute request", e);
        }
        if (!response.isSuccessful()) {
            throw handleErrorResponse(response);
        }
        return response;
    }

    private  T parseResponseBody(ApiRequest request, Response response) {
        try {
            String responseBody = response.body().string();
            return request.parseResponse(responseBody, responseParser);
        } catch (IOException e) {
            throw new GoCardlessNetworkException("Failed to read response body", e);
        }
    }

    private GoCardlessException handleErrorResponse(Response response) {
        try {
            String responseBody = response.body().string();
            return responseParser.parseError(responseBody);
        } catch (IOException e) {
            throw new GoCardlessNetworkException("Failed to read response body", e);
        }
    }

    private static String cleanUserAgentToken(String s) {
        return s.replaceAll(DISALLOWED_USER_AGENT_CHARACTERS, "_");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy