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

com.outbrain.ob1k.http.HttpClient Maven / Gradle / Ivy

The newest version!
package com.outbrain.ob1k.http;

import static com.google.common.base.Preconditions.checkNotNull;

import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.providers.netty.NettyAsyncHttpProviderConfig;

import com.outbrain.ob1k.http.marshalling.JacksonMarshallingStrategy;
import com.outbrain.ob1k.http.marshalling.MarshallingStrategy;
import com.outbrain.ob1k.http.ning.NingRequestBuilder;
import com.outbrain.swinfra.metrics.api.MetricFactory;

import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.util.HashedWheelTimer;

import java.io.Closeable;
import java.io.IOException;

/**
 * Ob1k's Http Client
 *
 * Provides a simple async client for your http requests,
 * with shared client configuration and specific request configuration.
 *
 * Using AsyncHttpClient as the executor
 *
 * @author marenzon, insipred of aronen's previous HttpClient implemintation.
 */
public class HttpClient implements Closeable {

  public static final int CHUNKED_FILE_CHUNK_SIZE = 65536;
  public static final int RETRIES = 3;
  public static final int CONNECTION_TIMEOUT = 200;
  public static final int REQUEST_TIMEOUT = 500;
  public static final int MAX_CONNECTIONS_PER_HOST = 100;
  public static final int MAX_TOTAL_CONNECTIONS = MAX_CONNECTIONS_PER_HOST * 2;

  private final AsyncHttpClient asyncHttpClient;
  private final MetricFactory metricFactory;
  private final MarshallingStrategy marshallingStrategy;
  private final long responseMaxSize;

  private HttpClient(final AsyncHttpClient asyncHttpClient, final MetricFactory metricFactory,
                     final long responseMaxSize, final MarshallingStrategy marshallingStrategy) {

    this.asyncHttpClient = asyncHttpClient;
    this.metricFactory = metricFactory;
    this.responseMaxSize = responseMaxSize;
    this.marshallingStrategy = marshallingStrategy;
  }

  /**
   * Creates new instance of HttpClient with default configuration
   *
   * @return new HttpClient
   */
  public static HttpClient createDefault() {

    return HttpClient.newBuilder().build();
  }

  /**
   * Http get request
   *
   * @param url url for the request
   * @return request builder
   */
  public RequestBuilder get(final String url) {

    checkNotNull(url, "url may not be null");
    final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder = asyncHttpClient.prepareGet(url);
    return createNewRequestBuilder(url, ningRequestBuilder);
  }

  /**
   * Http post request
   *
   * @param url url for the request
   * @return request builder
   */
  public RequestBuilder post(final String url) {

    checkNotNull(url, "url may not be null");
    final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder = asyncHttpClient.preparePost(url);
    return createNewRequestBuilder(url, ningRequestBuilder);
  }

  /**
   * Http put request
   *
   * @param url url for the request
   * @return request builder
   */
  public RequestBuilder put(final String url) {

    checkNotNull(url, "url may not be null");
    final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder = asyncHttpClient.preparePut(url);
    return createNewRequestBuilder(url, ningRequestBuilder);
  }

  /**
   * Http delete request
   *
   * @param url url for the request
   * @return request builder
   */
  public RequestBuilder delete(final String url) {

    checkNotNull(url, "url may not be null");
    final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder = asyncHttpClient.prepareDelete(url);
    return createNewRequestBuilder(url, ningRequestBuilder);
  }

  /**
   * Http head request
   *
   * @param url url for the request
   * @return request builder
   */
  public RequestBuilder head(final String url) {

    checkNotNull(url, "url may not be null");
    final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder = asyncHttpClient.prepareHead(url);
    return createNewRequestBuilder(url, ningRequestBuilder);
  }

  /**
   * Closing the http client
   *
   * @throws IOException
   */
  @Override
  public void close() throws IOException {

    asyncHttpClient.close();
  }

  private NingRequestBuilder createNewRequestBuilder(final String url, final AsyncHttpClient.BoundRequestBuilder ningRequestBuilder) {

    return new NingRequestBuilder(asyncHttpClient, ningRequestBuilder, url, metricFactory, responseMaxSize, marshallingStrategy);
  }

  /**
   * @return new builder object
   */
  public static Builder newBuilder() {

    return new Builder();
  }

  /**
   * Builder for the HttpClient configuration
   * Creates a new HttpClient instance.
   */
  public static class Builder {

    private MarshallingStrategy marshallingStrategy = new JacksonMarshallingStrategy();
    private MetricFactory metricFactory;
    private int connectionTimeout = HttpClient.CONNECTION_TIMEOUT;
    private int requestTimeout = HttpClient.REQUEST_TIMEOUT;
    private Integer readTimeout = null;
    private int retries = HttpClient.RETRIES;
    private int maxConnectionsPerHost = HttpClient.MAX_CONNECTIONS_PER_HOST;
    private int maxTotalConnections = HttpClient.MAX_TOTAL_CONNECTIONS;
    private boolean compressionEnforced;
    private boolean disableUrlEncoding;
    private boolean followRedirect;
    private boolean acceptAnySslCertificate;
    private long responseMaxSize;

    /**
     * Max retries for request
     *
     * @param retries retries count
     * @return builder
     */
    public Builder setRetries(final int retries) {

      this.retries = retries;
      return this;
    }

    /**
     * Max number of connections per host
     *
     * @param maxConnectionsPerHost the max connections number per host
     * @return builder
     */
    public Builder setMaxConnectionsPerHost(final int maxConnectionsPerHost) {

      this.maxConnectionsPerHost = maxConnectionsPerHost;
      return this;
    }

    /**
     * Response max size - in case of large response that may cause OOM
     *
     * @param responseMaxSize response max size
     * @return builder
     */
    public Builder setResponseMaxSize(final long responseMaxSize) {

      this.responseMaxSize = responseMaxSize;
      return this;
    }

    /**
     * Max number of total connections
     *
     * @param maxTotalConnections the max total connections number
     * @return builder
     */
    public Builder setMaxTotalConnections(final int maxTotalConnections) {

      this.maxTotalConnections = maxTotalConnections;
      return this;
    }

    /**
     * Accepting all SSL certificates, even if they invalid
     * Note: NOT recommended to use, since it's may cause potential security issue.
     *
     * @param acceptAnySslCertificate accept any ssl certificate boolean
     * @see ssl cetificate validation vulnerabilities
     * @return builder
     */
    public Builder setAcceptAnySslCertificate(final boolean acceptAnySslCertificate) {

      this.acceptAnySslCertificate = acceptAnySslCertificate;
      return this;
    }

    /**
     * Connection timeout
     *
     * @param connectionTimeout connection timeout in ms
     * @return builder
     */
    public Builder setConnectionTimeout(final int connectionTimeout) {

      this.connectionTimeout = connectionTimeout;
      return this;
    }

    /**
     * Request timeout
     * Can be override in request builder phase
     *
     * @param requestTimeout request timeout in ms
     * @return builder
     */
    public Builder setRequestTimeout(final int requestTimeout) {

      this.requestTimeout = requestTimeout;
      return this;
    }

    /**
     * Read timeout (optional, defaults to 60 sec).
     *
     * @param readTimeout read timeout in ms
     * @return builder
     */
    public Builder setReadTimeout(final int readTimeout) {

      this.readTimeout = readTimeout;
      return this;
    }

    /**
     * Enforce compression on the request
     *
     * @param compressionEnforced enforce compression
     * @return builder
     */
    public Builder setCompressionEnforced(final boolean compressionEnforced) {

      this.compressionEnforced = compressionEnforced;
      return this;
    }

    /**
     * Disable url encoding
     *
     * @param disableUrlEncoding disable url encoding
     * @return builder
     */
    public Builder setDisableUrlEncoding(final boolean disableUrlEncoding) {

      this.disableUrlEncoding = disableUrlEncoding;
      return this;
    }

    /**
     * Follow redirects
     *
     * @param followRedirect follow redirects
     * @return builder
     */
    public Builder setFollowRedirect(final boolean followRedirect) {

      this.followRedirect = followRedirect;
      return this;
    }

    /**
     * Set metric factory for the client
     *
     * @param metricFactory metric factory
     * @return builder
     */
    public Builder setMetricFactory(final MetricFactory metricFactory) {

      this.metricFactory = metricFactory;
      return this;
    }

    /**
     * Set marshalling strategy default for the request builder
     *
     * @param marshallingStrategy marshalling strategy
     */
    public Builder setMarshallingStrategy(final MarshallingStrategy marshallingStrategy) {

      this.marshallingStrategy = checkNotNull(marshallingStrategy, "marshallingStrategy may not be null");
      return this;
    }

    /**
     * Creates new HttpClient from the configuration set
     *
     * @return new HttpClient instance
     */
    public HttpClient build() {

      final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().
        setConnectTimeout(connectionTimeout).
        setMaxRequestRetry(retries).
        setRequestTimeout(requestTimeout).
        setCompressionEnforced(compressionEnforced).
        setDisableUrlEncodingForBoundedRequests(disableUrlEncoding).
        setMaxConnectionsPerHost(maxConnectionsPerHost).
        setMaxConnections(maxTotalConnections).
        setAsyncHttpClientProviderConfig(NettyConfigHolder.INSTANCE).
        setFollowRedirect(followRedirect).
        setAcceptAnyCertificate(acceptAnySslCertificate);

      if (readTimeout != null) {
        configBuilder.setReadTimeout(readTimeout);
      }

      return new HttpClient(new AsyncHttpClient(configBuilder.
        build()), metricFactory, responseMaxSize, marshallingStrategy);
    }
  }

  /**
   * A singleton of Netty's httpProvider config, so all the clients
   * will share the same thread pool for all the executions.
   *
   * @author aronen
   */
  private static class NettyConfigHolder {

    private static final NettyAsyncHttpProviderConfig INSTANCE = createConfig();

    private static NettyAsyncHttpProviderConfig createConfig() {

      final NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig();
      final NioClientSocketChannelFactory channelFactory = new NioClientSocketChannelFactory();

      nettyConfig.setSocketChannelFactory(channelFactory);
      nettyConfig.setChunkedFileChunkSize(CHUNKED_FILE_CHUNK_SIZE);

      final HashedWheelTimer timer = new HashedWheelTimer();
      timer.start();
      nettyConfig.setNettyTimer(timer);

      registerShutdownHook(channelFactory, timer);
      return nettyConfig;
    }

    private static void registerShutdownHook(final NioClientSocketChannelFactory channelFactory, final HashedWheelTimer hashedWheelTimer) {

      Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
          channelFactory.shutdown();
          channelFactory.releaseExternalResources();
          hashedWheelTimer.stop();
        }
      });
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy