package io.dropwizard.client;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategies;
import com.codahale.metrics.httpclient.HttpClientMetricNameStrategy;
import com.codahale.metrics.httpclient.InstrumentedHttpClientConnectionManager;
import com.codahale.metrics.httpclient.InstrumentedHttpRequestExecutor;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.client.proxy.AuthConfiguration;
import io.dropwizard.client.proxy.NonProxyListProxyRoutePlanner;
import io.dropwizard.client.proxy.ProxyConfiguration;
import io.dropwizard.client.ssl.TlsConfiguration;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.setup.Environment;
import io.dropwizard.util.Duration;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import java.util.List;
public class HttpClientBuilder {
private static final HttpRequestRetryHandler NO_RETRIES = (exception, executionCount, context) -> false;
private final MetricRegistry metricRegistry;
@Nullable
private String environmentName;
@Nullable
private Environment environment;
private HttpClientConfiguration configuration = new HttpClientConfiguration();
private DnsResolver resolver = new SystemDefaultDnsResolver();
@Nullable
private HostnameVerifier verifier;
@Nullable
private HttpRequestRetryHandler httpRequestRetryHandler;
@Nullable
private Registry<ConnectionSocketFactory> registry;
@Nullable
private CredentialsProvider credentialsProvider;
private HttpClientMetricNameStrategy metricNameStrategy = HttpClientMetricNameStrategies.METHOD_ONLY;
@Nullable
private HttpRoutePlanner routePlanner;
@Nullable
private RedirectStrategy redirectStrategy;
private boolean disableContentCompression;
@Nullable
private List<? extends Header> ;
@Nullable
private HttpProcessor httpProcessor;
@Nullable
private ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy;
public HttpClientBuilder(MetricRegistry metricRegistry) {
this.metricRegistry = metricRegistry;
}
public HttpClientBuilder(Environment environment) {
this(environment.metrics());
name(environment.getName());
this.environment = environment;
}
public HttpClientBuilder name(String environmentName) {
this.environmentName = environmentName;
return this;
}
public HttpClientBuilder using(HttpClientConfiguration configuration) {
this.configuration = configuration;
return this;
}
public HttpClientBuilder using(DnsResolver resolver) {
this.resolver = resolver;
return this;
}
public HttpClientBuilder using(HostnameVerifier verifier) {
this.verifier = verifier;
return this;
}
public HttpClientBuilder using(HttpRequestRetryHandler httpRequestRetryHandler) {
this.httpRequestRetryHandler = httpRequestRetryHandler;
return this;
}
public HttpClientBuilder using(Registry<ConnectionSocketFactory> registry) {
this.registry = registry;
return this;
}
public HttpClientBuilder using(HttpRoutePlanner routePlanner) {
this.routePlanner = routePlanner;
return this;
}
public HttpClientBuilder using(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
return this;
}
public HttpClientBuilder using(HttpClientMetricNameStrategy metricNameStrategy) {
this.metricNameStrategy = metricNameStrategy;
return this;
}
public HttpClientBuilder using(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
return this;
}
public HttpClientBuilder using(List<? extends Header> defaultHeaders) {
this.defaultHeaders = defaultHeaders;
return this;
}
public HttpClientBuilder using(HttpProcessor httpProcessor) {
this.httpProcessor = httpProcessor;
return this;
}
public HttpClientBuilder using(ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy) {
this.serviceUnavailableRetryStrategy = serviceUnavailableRetryStrategy;
return this;
}
public HttpClientBuilder disableContentCompression(boolean disableContentCompression) {
this.disableContentCompression = disableContentCompression;
return this;
}
public CloseableHttpClient build(String name) {
final CloseableHttpClient client = buildWithDefaultRequestConfiguration(name).getClient();
if (environment != null) {
environment.lifecycle().manage(new Managed() {
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
client.close();
}
});
}
return client;
}
ConfiguredCloseableHttpClient buildWithDefaultRequestConfiguration(String name) {
return createClient(org.apache.http.impl.client.HttpClientBuilder.create(),
createConnectionManager(createConfiguredRegistry(), name), name);
}
protected org.apache.http.impl.client.HttpClientBuilder customizeBuilder(
org.apache.http.impl.client.HttpClientBuilder builder
) {
return builder;
}
protected ConfiguredCloseableHttpClient createClient(
final org.apache.http.impl.client.HttpClientBuilder builder,
final InstrumentedHttpClientConnectionManager manager,
final String name) {
final String cookiePolicy = configuration.isCookiesEnabled() ? CookieSpecs.DEFAULT : CookieSpecs.IGNORE_COOKIES;
final Integer timeout = (int) configuration.getTimeout().toMilliseconds();
final Integer connectionTimeout = (int) configuration.getConnectionTimeout().toMilliseconds();
final Integer connectionRequestTimeout = (int) configuration.getConnectionRequestTimeout().toMilliseconds();
final long keepAlive = configuration.getKeepAlive().toMilliseconds();
final ConnectionReuseStrategy reuseStrategy = keepAlive == 0
? new NoConnectionReuseStrategy()
: new DefaultConnectionReuseStrategy();
final HttpRequestRetryHandler retryHandler = configuration.getRetries() == 0
? NO_RETRIES
: (httpRequestRetryHandler == null ? new DefaultHttpRequestRetryHandler(configuration.getRetries(),
false) : httpRequestRetryHandler);
final RequestConfig requestConfig
= RequestConfig.custom().setCookieSpec(cookiePolicy)
.setSocketTimeout(timeout)
.setConnectTimeout(connectionTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.build();
final SocketConfig socketConfig = SocketConfig.custom()
.setTcpNoDelay(true)
.setSoTimeout(timeout)
.build();
customizeBuilder(builder)
.setRequestExecutor(new InstrumentedHttpRequestExecutor(metricRegistry, metricNameStrategy, name))
.setConnectionManager(manager)
.setDefaultRequestConfig(requestConfig)
.setDefaultSocketConfig(socketConfig)
.setConnectionReuseStrategy(reuseStrategy)
.setRetryHandler(retryHandler)
.setUserAgent(createUserAgent(name));
if (keepAlive != 0) {
builder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
final long duration = super.getKeepAliveDuration(response, context);
return (duration == -1) ? keepAlive : duration;
}
});
}
final ProxyConfiguration proxy = configuration.getProxyConfiguration();
if (proxy != null) {
final HttpHost httpHost = new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getScheme());
builder.setRoutePlanner(new NonProxyListProxyRoutePlanner(httpHost, proxy.getNonProxyHosts()));
final AuthConfiguration auth = proxy.getAuth();
if (auth != null) {
if (credentialsProvider == null) {
credentialsProvider = new BasicCredentialsProvider();
}
AuthScope authScope = new AuthScope(httpHost, auth.getRealm(), auth.getAuthScheme());
Credentials credentials = configureCredentials(auth);
credentialsProvider.setCredentials(authScope, credentials);
}
}
if (credentialsProvider != null) {
builder.setDefaultCredentialsProvider(credentialsProvider);
}
if (routePlanner != null) {
builder.setRoutePlanner(routePlanner);
}
if (disableContentCompression) {
builder.disableContentCompression();
}
if (redirectStrategy != null) {
builder.setRedirectStrategy(redirectStrategy);
}
if (defaultHeaders != null) {
builder.setDefaultHeaders(defaultHeaders);
}
if (verifier != null) {
builder.setSSLHostnameVerifier(verifier);
}
if (httpProcessor != null) {
builder.setHttpProcessor(httpProcessor);
}
if (serviceUnavailableRetryStrategy != null) {
builder.setServiceUnavailableRetryStrategy(serviceUnavailableRetryStrategy);
}
return new ConfiguredCloseableHttpClient(builder.build(), requestConfig);
}
protected String createUserAgent(String name) {
final String defaultUserAgent = environmentName == null ? name : String.format("%s (%s)", environmentName, name);
return configuration.getUserAgent().orElse(defaultUserAgent);
}
protected InstrumentedHttpClientConnectionManager createConnectionManager(Registry<ConnectionSocketFactory> registry,
String name) {
final Duration ttl = configuration.getTimeToLive();
final InstrumentedHttpClientConnectionManager manager = new InstrumentedHttpClientConnectionManager(
metricRegistry,
registry,
null, null,
resolver,
ttl.getQuantity(),
ttl.getUnit(),
name);
return configureConnectionManager(manager);
}
@VisibleForTesting
Registry<ConnectionSocketFactory> createConfiguredRegistry() {
if (registry != null) {
return registry;
}
TlsConfiguration tlsConfiguration = configuration.getTlsConfiguration();
if (tlsConfiguration == null && verifier != null) {
tlsConfiguration = new TlsConfiguration();
}
final SSLConnectionSocketFactory sslConnectionSocketFactory;
if (tlsConfiguration == null) {
sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
} else {
sslConnectionSocketFactory = new DropwizardSSLConnectionSocketFactory(tlsConfiguration,
verifier).getSocketFactory();
}
return RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build();
}
@VisibleForTesting
protected InstrumentedHttpClientConnectionManager configureConnectionManager(
InstrumentedHttpClientConnectionManager connectionManager) {
connectionManager.setDefaultMaxPerRoute(configuration.getMaxConnectionsPerRoute());
connectionManager.setMaxTotal(configuration.getMaxConnections());
connectionManager.setValidateAfterInactivity((int) configuration.getValidateAfterInactivityPeriod().toMilliseconds());
return connectionManager;
}
protected Credentials configureCredentials(AuthConfiguration auth) {
if (null != auth.getCredentialType() && auth.getCredentialType().equalsIgnoreCase(AuthConfiguration.NT_CREDS)) {
return new NTCredentials(auth.getUsername(), auth.getPassword(), auth.getHostname(), auth.getDomain());
} else {
return new UsernamePasswordCredentials(auth.getUsername(), auth.getPassword());
}
}
}