/*
* COMSAT
* Copyright (C) 2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.fibers.dropwizard;
import co.paralleluniverse.fibers.httpclient.FiberHttpClient;
import com.codahale.metrics.MetricRegistry;
import io.dropwizard.client.HttpClientConfiguration;
import io.dropwizard.setup.Environment;
import io.dropwizard.util.Duration;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
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.conn.DnsResolver;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLInitializationException;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NHttpClientConnectionManager;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.protocol.HttpContext;
/**
* A convenience class for building {@link HttpClient} instances.
* <p>
* Among other things,
* <ul>
* <li>Disables stale connection checks</li>
* <li>Disables Nagle's algorithm</li>
* <li>Disables cookie management by default</li>
* </ul>
*/
public class FiberHttpClientBuilder {
private final MetricRegistry metricRegistry;
private HttpClientConfiguration configuration = new HttpClientConfiguration();
private DnsResolver resolver = new SystemDefaultDnsResolver();
private HttpRequestRetryHandler httpRequestRetryHandler;
private SchemeRegistry registry = SchemeRegistryFactory.createSystemDefault();
public FiberHttpClientBuilder(MetricRegistry metricRegistry) {
this.metricRegistry = metricRegistry;
}
public FiberHttpClientBuilder(Environment environment) {
this.metricRegistry = environment.metrics();
}
/**
* Use the given {@link HttpClientConfiguration} instance.
*
* @param configuration a {@link HttpClientConfiguration} instance
* @return {@code this}
*/
public FiberHttpClientBuilder using(HttpClientConfiguration configuration) {
this.configuration = configuration;
return this;
}
/**
* Use the given {@link DnsResolver} instance.
*
* @param resolver a {@link DnsResolver} instance
* @return {@code this}
*/
public FiberHttpClientBuilder using(DnsResolver resolver) {
this.resolver = resolver;
return this;
}
/**
* Uses the {@link httpRequestRetryHandler} for handling request retries.
*
* @param httpRequestRetryHandler an httpRequestRetryHandler
* @return {@code this}
*/
public FiberHttpClientBuilder using(HttpRequestRetryHandler httpRequestRetryHandler) {
this.httpRequestRetryHandler = httpRequestRetryHandler;
return this;
}
/**
* Use the given {@link SchemeRegistry} instance.
*
* @param registry a {@link SchemeRegistry} instance
* @return {@code this}
*/
public FiberHttpClientBuilder using(SchemeRegistry registry) {
this.registry = registry;
return this;
}
/**
* Builds the {@link HttpClient}.
*
* @return an {@link HttpClient}
*/
public HttpClient build(String name) {
RequestConfig createHttpParams = createHttpParams();
final NHttpClientConnectionManager manager = createConnectionManager(registry, name);
HttpAsyncClientBuilder clientBuilder = new InstrumentedNHttpClientBuilder(metricRegistry, name);
clientBuilder.setConnectionManager(manager);
clientBuilder.setDefaultRequestConfig(createHttpParams);
setStrategiesForClient(clientBuilder);
CloseableHttpAsyncClient client = clientBuilder.build();
client.start();
return new FiberHttpClient(client, getRetryHandler());
}
/**
* Add strategies to client such as ConnectionReuseStrategy and KeepAliveStrategy Note that this
* method mutates the client object by setting the strategies
*
* @param client The InstrumentedHttpClient that should be configured with strategies
*/
protected void setStrategiesForClient(HttpAsyncClientBuilder client) {
final long keepAlive = configuration.getKeepAlive().toMilliseconds();
// don't keep alive the HTTP connection and thus don't reuse the TCP socket
if (keepAlive == 0) {
client.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
} else {
client.setConnectionReuseStrategy(new DefaultConnectionReuseStrategy());
// either keep alive based on response header Keep-Alive,
// or if the server can keep a persistent connection (-1), then override based on client's configuration
client.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
final long duration = super.getKeepAliveDuration(response, context);
return (duration == -1) ? keepAlive : duration;
}
});
}
}
private HttpRequestRetryHandler getRetryHandler() {
return configuration.getRetries() == 0 ? null
: httpRequestRetryHandler != null ? httpRequestRetryHandler
: new DefaultHttpRequestRetryHandler(configuration.getRetries(), false);
}
/**
* Map the parameters in HttpClientConfiguration to a BasicHttpParams object
*
* @return a BasicHttpParams object from the HttpClientConfiguration
*/
protected RequestConfig createHttpParams() {
RequestConfig.Builder rcb = RequestConfig.custom();
rcb.setCookieSpec(CookieSpecs.BEST_MATCH);
if (configuration.isCookiesEnabled())
rcb.setCookieSpec(CookieSpecs.BEST_MATCH);
else
rcb.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
rcb.setStaleConnectionCheckEnabled(false);
return rcb.build();
}
/**
* Create a InstrumentedClientConnManager based on the HttpClientConfiguration. It sets the
* maximum connections per route and the maximum total connections that the connection manager
* can create
*
* @param registry the SchemeRegistry
* @return a InstrumentedClientConnManger instance
*/
protected NHttpClientConnectionManager createConnectionManager(SchemeRegistry registry, String name) {
final Duration ttl = configuration.getTimeToLive();
ConnectingIOReactor ioReactor = createDefaultIOReactor(IOReactorConfig.custom()
.setSoTimeout((int) configuration.getTimeout().toMilliseconds())
.setConnectTimeout((int) configuration.getConnectionTimeout().toMilliseconds())
.setTcpNoDelay(true).build());
PoolingNHttpClientConnectionManager manager
= new InstrumentedNClientConnManager(
ioReactor, null, null, //TODO: add this parameters values
metricRegistry,
convertRegistry(this.registry),
ttl.getQuantity(),
ttl.getUnit(),
resolver,
name);
manager.setDefaultMaxPerRoute(configuration.getMaxConnectionsPerRoute());
manager.setMaxTotal(configuration.getMaxConnections());
return manager;
}
private static ConnectingIOReactor createDefaultIOReactor(final IOReactorConfig spec) {
try {
return new DefaultConnectingIOReactor(spec);
} catch (IOReactorException ex) {
throw new RuntimeException(ex);
}
}
private static Registry<SchemeIOSessionStrategy> convertRegistry(final SchemeRegistry oldRegistry) throws SSLInitializationException {
SchemeRegistry baseRegistry = oldRegistry;
//TODO: use values from old registry;
Registry<SchemeIOSessionStrategy> defaultRegistry = RegistryBuilder.<SchemeIOSessionStrategy>create()
.register("http", NoopIOSessionStrategy.INSTANCE)
.register("https", new SSLIOSessionStrategy(
SSLContexts.createDefault(), null, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER))
.build();
return defaultRegistry;
}
}