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

com.hubspot.horizon.apache.ApacheHttpClient Maven / Gradle / Ivy

There is a newer version: 0.3.1
Show newest version
package com.hubspot.horizon.apache;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.hubspot.horizon.HttpClient;
import com.hubspot.horizon.HttpConfig;
import com.hubspot.horizon.HttpRequest;
import com.hubspot.horizon.HttpRequest.Options;
import com.hubspot.horizon.HttpResponse;
import com.hubspot.horizon.HttpRuntimeException;
import com.hubspot.horizon.RetryHelper;
import com.hubspot.horizon.RetryStrategy;
import com.hubspot.horizon.apache.internal.ApacheHttpRequestConverter;
import com.hubspot.horizon.apache.internal.ApacheHttpResponse;
import com.hubspot.horizon.apache.internal.ApacheSSLSocketFactory;
import com.hubspot.horizon.apache.internal.CachedHttpResponse;
import com.hubspot.horizon.apache.internal.DefaultHeadersRequestInterceptor;
import com.hubspot.horizon.apache.internal.KeepAliveWithDefaultStrategy;
import com.hubspot.horizon.apache.internal.LenientRedirectStrategy;
import com.hubspot.horizon.apache.internal.SnappyContentEncodingResponseInterceptor;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

public class ApacheHttpClient implements HttpClient {
  private static final Logger LOG = LoggerFactory.getLogger(ApacheHttpClient.class);

  private final CloseableHttpClient apacheClient;
  private final ApacheHttpRequestConverter requestConverter;
  private final HttpConfig config;
  private final Options defaultOptions;
  private final Timer timer;

  public ApacheHttpClient() {
    this(HttpConfig.newBuilder().build());
  }

  public ApacheHttpClient(HttpConfig config) {
    Preconditions.checkNotNull(config);

    HttpClientBuilder builder = HttpClientBuilder.create();

    builder.setConnectionManager(createConnectionManager(config));
    builder.setRedirectStrategy(new LenientRedirectStrategy());
    builder.setKeepAliveStrategy(new KeepAliveWithDefaultStrategy(config.getDefaultKeepAliveMillis()));
    builder.addInterceptorFirst(new DefaultHeadersRequestInterceptor(config));
    builder.addInterceptorFirst(new SnappyContentEncodingResponseInterceptor());
    builder.setDefaultRequestConfig(createRequestConfig(config));
    builder.setDefaultSocketConfig(createSocketConfig(config));
    builder.disableContentCompression();

    this.apacheClient = builder.build();
    this.requestConverter = new ApacheHttpRequestConverter(config.getObjectMapper());
    this.config = config;
    this.defaultOptions = config.getOptions();
    this.timer = new Timer("http-request-timeout", true);
  }

  private HttpClientConnectionManager createConnectionManager(HttpConfig config) {
    Registry registry = createSocketFactoryRegistry(config);
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
    connectionManager.setMaxTotal(config.getMaxConnections());
    connectionManager.setDefaultMaxPerRoute(config.getMaxConnectionsPerHost());

    return connectionManager;
  }

  private Registry createSocketFactoryRegistry(HttpConfig config) {
    RegistryBuilder builder = RegistryBuilder.create();

    builder.register("http", PlainConnectionSocketFactory.getSocketFactory());
    builder.register("https", ApacheSSLSocketFactory.forConfig(config.getSSLConfig()));

    return builder.build();
  }

  private RequestConfig createRequestConfig(HttpConfig config) {
    return RequestConfig.custom()
            .setConnectionRequestTimeout(config.getConnectTimeoutMillis())
            .setConnectTimeout(config.getConnectTimeoutMillis())
            .setSocketTimeout(config.getRequestTimeoutMillis())
            .setRedirectsEnabled(config.isFollowRedirects())
            .setMaxRedirects(config.getMaxRedirects())
            .setRelativeRedirectsAllowed(config.isRejectRelativeRedirects())
            .build();
  }

  private SocketConfig createSocketConfig(HttpConfig config) {
    return SocketConfig.custom().setSoTimeout(config.getRequestTimeoutMillis()).build();
  }

  @Override
  public HttpResponse execute(HttpRequest request) throws HttpRuntimeException {
    return execute(Preconditions.checkNotNull(request), Options.DEFAULT);
  }

  @Override
  public HttpResponse execute(HttpRequest request, Options options) throws HttpRuntimeException {
    Preconditions.checkNotNull(request);
    Preconditions.checkNotNull(options);

    try {
      return executeWithRetries(request, defaultOptions.mergeFrom(options), 0);
    } catch (Exception e) {
      Throwables.propagateIfInstanceOf(e, HttpRuntimeException.class);
      throw new HttpRuntimeException(e);
    }
  }

  private HttpResponse executeWithRetries(HttpRequest request, Options options, int retries) throws IOException {
    int maxRetries = options.getMaxRetries();
    RetryStrategy retryStrategy = options.getRetryStrategy();

    HttpUriRequest apacheRequest = requestConverter.convert(request);
    org.apache.http.HttpResponse apacheResponse = null;
    AtomicBoolean timedOut = new AtomicBoolean(false);
    try {
      TimerTask timeoutTask = setupTimeoutTask(apacheRequest, timedOut);

      final HttpResponse response;
      try {
        apacheResponse = apacheClient.execute(apacheRequest);
        response = CachedHttpResponse.from(new ApacheHttpResponse(request, apacheResponse, config.getObjectMapper()));
      } finally {
        // once this is done the timeout can be canceled
        timeoutTask.cancel();
      }

      if (retries < maxRetries && retryStrategy.shouldRetry(request, response)) {
        LOG.warn(String.format("Going to retry failed request to '%s' (Status: %d)", request.getUrl(), response.getStatusCode()));
      } else {
        return response;
      }
    } catch (ClientProtocolException e) {
      throw e;
    } catch (IOException e) {
      if (timedOut.get()) {
        close(apacheResponse);
        e = new IOException(new TimeoutException(String.format("Request to '%s' timed out", request.getUrl())));
      }

      if (retries < maxRetries && retryStrategy.shouldRetry(request, e)) {
        LOG.warn(String.format("Going to retry failed request to '%s'", request.getUrl()), e);
      } else {
        throw e;
      }
    }

    return backoffAndRetry(request, options, retries + 1);
  }

  private HttpResponse backoffAndRetry(HttpRequest request, Options options, int retries) throws IOException {
    try {
      Thread.sleep(RetryHelper.computeBackoff(options, retries));
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new HttpRuntimeException(e);
    }

    return executeWithRetries(request, options, retries);
  }

  private TimerTask setupTimeoutTask(final HttpUriRequest request, final AtomicBoolean timedOut) {
    int delay = config.getConnectTimeoutMillis() + config.getRequestTimeoutMillis();
    TimerTask timeoutTask = new TimerTask() {

      @Override
      public void run() {
        timedOut.set(true);
        request.abort();
      }
    };
    timer.schedule(timeoutTask, delay);

    return timeoutTask;
  }

  private void close(@Nullable org.apache.http.HttpResponse response) {
    if (response != null) {
      try {
        // make sure these resources are returned
        HttpEntity entity = response.getEntity();
        if (entity != null) {
          InputStream responseStream = entity.getContent();
          if (responseStream != null) {
            responseStream.close();
          }
        }
      } catch (Exception e) {
        LOG.warn("Error closing Apache response", e);
      }
    }
  }

  @Override
  public void close() throws IOException {
    apacheClient.close();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy