package io.dropwizard.client; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.validateMockitoUsage; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.util.List; import java.util.Optional; import javax.net.ssl.HostnameVerifier; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.http.Header; import org.apache.http.HeaderIterator; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.ProtocolException; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.RedirectStrategy; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; 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.DnsResolver; import org.apache.http.conn.routing.HttpRoute; 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.CloseableHttpClient; import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; import org.apache.http.impl.conn.DefaultRoutePlanner; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.SystemDefaultDnsResolver; import org.apache.http.impl.conn.SystemDefaultRoutePlanner; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicListHeaderIterator; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpProcessor; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.httpclient.HttpClientMetricNameStrategies; import com.codahale.metrics.httpclient.InstrumentedHttpClientConnectionManager; import com.codahale.metrics.httpclient.InstrumentedHttpRequestExecutor; import com.google.common.collect.ImmutableList; import io.dropwizard.client.proxy.AuthConfiguration; import io.dropwizard.client.proxy.ProxyConfiguration; import io.dropwizard.client.ssl.TlsConfiguration; import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.setup.LifecycleEnvironment; import io.dropwizard.setup.Environment; import io.dropwizard.util.Duration; public class HttpClientBuilderTest { static class CustomBuilder extends HttpClientBuilder { public boolean customized; public CustomBuilder(MetricRegistry metricRegistry) { super(metricRegistry); customized = false; } @Override protected org.apache.http.impl.client.HttpClientBuilder customizeBuilder( org.apache.http.impl.client.HttpClientBuilder builder ) { customized = true; return builder; } } private final Class<?> httpClientBuilderClass; private final Class<?> httpClientClass; private final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); private HttpClientConfiguration configuration; private HttpClientBuilder builder; private InstrumentedHttpClientConnectionManager connectionManager; private org.apache.http.impl.client.HttpClientBuilder apacheBuilder; public HttpClientBuilderTest() throws ClassNotFoundException { this.httpClientBuilderClass = Class.forName("org.apache.http.impl.client.HttpClientBuilder"); this.httpClientClass = Class.forName("org.apache.http.impl.client.InternalHttpClient"); } @Before public void setUp() { final MetricRegistry metricRegistry = new MetricRegistry(); configuration = new HttpClientConfiguration(); builder = new HttpClientBuilder(metricRegistry); connectionManager = spy(new InstrumentedHttpClientConnectionManager(metricRegistry, registry)); apacheBuilder = org.apache.http.impl.client.HttpClientBuilder.create(); initMocks(this); } @After public void validate() { validateMockitoUsage(); } @Test public void setsTheMaximumConnectionPoolSize() throws Exception { configuration.setMaxConnections(412); final ConfiguredCloseableHttpClient client = builder.using(configuration) .createClient(apacheBuilder, builder.configureConnectionManager(connectionManager), "test"); assertThat(client).isNotNull(); assertThat(spyHttpClientBuilderField("connManager", apacheBuilder)).isSameAs(connectionManager); verify(connectionManager).setMaxTotal(412); } @Test public void setsTheMaximumRoutePoolSize() throws Exception { configuration.setMaxConnectionsPerRoute(413); final ConfiguredCloseableHttpClient client = builder.using(configuration) .createClient(apacheBuilder, builder.configureConnectionManager(connectionManager), "test"); assertThat(client).isNotNull(); assertThat(spyHttpClientBuilderField("connManager", apacheBuilder)).isSameAs(connectionManager); verify(connectionManager).setDefaultMaxPerRoute(413); } @Test public void setsTheUserAgent() throws Exception { configuration.setUserAgent(Optional.of("qwerty")); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(spyHttpClientBuilderField("userAgent", apacheBuilder)).isEqualTo("qwerty"); } @Test public void canUseACustomDnsResolver() throws Exception { final DnsResolver resolver = mock(DnsResolver.class); final InstrumentedHttpClientConnectionManager manager = builder.using(resolver).createConnectionManager(registry, "test"); // Yes, this is gross. Thanks, Apache! final Field connectionOperatorField = FieldUtils.getField(PoolingHttpClientConnectionManager.class, "connectionOperator", true); final Object connectOperator = connectionOperatorField.get(manager); final Field dnsResolverField = FieldUtils.getField(connectOperator.getClass(), "dnsResolver", true); assertThat(dnsResolverField.get(connectOperator)).isEqualTo(resolver); } @Test public void usesASystemDnsResolverByDefault() throws Exception { final InstrumentedHttpClientConnectionManager manager = builder.createConnectionManager(registry, "test"); // Yes, this is gross. Thanks, Apache! final Field connectionOperatorField = FieldUtils.getField(PoolingHttpClientConnectionManager.class, "connectionOperator", true); final Object connectOperator = connectionOperatorField.get(manager); final Field dnsResolverField = FieldUtils.getField(connectOperator.getClass(), "dnsResolver", true); assertThat(dnsResolverField.get(connectOperator)).isInstanceOf(SystemDefaultDnsResolver.class); } @Test public void canUseACustomHostnameVerifierWhenTlsConfigurationNotSpecified() throws Exception { final HostnameVerifier customVerifier = (s, sslSession) -> false; final Registry<ConnectionSocketFactory> configuredRegistry; configuredRegistry = builder.using(customVerifier).createConfiguredRegistry(); assertThat(configuredRegistry).isNotNull(); final SSLConnectionSocketFactory socketFactory = (SSLConnectionSocketFactory) configuredRegistry.lookup("https"); assertThat(socketFactory).isNotNull(); final Field hostnameVerifierField = FieldUtils.getField(SSLConnectionSocketFactory.class, "hostnameVerifier", true); assertThat(hostnameVerifierField.get(socketFactory)).isSameAs(customVerifier); } @Test public void canUseACustomHostnameVerifierWhenTlsConfigurationSpecified() throws Exception { final TlsConfiguration tlsConfiguration = new TlsConfiguration(); tlsConfiguration.setVerifyHostname(true); configuration.setTlsConfiguration(tlsConfiguration); final HostnameVerifier customVerifier = (s, sslSession) -> false; final Registry<ConnectionSocketFactory> configuredRegistry; configuredRegistry = builder.using(configuration).using(customVerifier).createConfiguredRegistry(); assertThat(configuredRegistry).isNotNull(); final SSLConnectionSocketFactory socketFactory = (SSLConnectionSocketFactory) configuredRegistry.lookup("https"); assertThat(socketFactory).isNotNull(); final Field hostnameVerifierField = FieldUtils.getField(SSLConnectionSocketFactory.class, "hostnameVerifier", true); assertThat(hostnameVerifierField.get(socketFactory)).isSameAs(customVerifier); } @Test public void canUseASystemHostnameVerifierByDefaultWhenTlsConfigurationNotSpecified() throws Exception { final Registry<ConnectionSocketFactory> configuredRegistry; configuredRegistry = builder.createConfiguredRegistry(); assertThat(configuredRegistry).isNotNull(); final SSLConnectionSocketFactory socketFactory = (SSLConnectionSocketFactory) configuredRegistry.lookup("https"); assertThat(socketFactory).isNotNull(); final Field hostnameVerifierField = FieldUtils.getField(SSLConnectionSocketFactory.class, "hostnameVerifier", true); assertThat(hostnameVerifierField.get(socketFactory)).isInstanceOf(HostnameVerifier.class); } @Test public void canUseASystemHostnameVerifierByDefaultWhenTlsConfigurationSpecified() throws Exception { final TlsConfiguration tlsConfiguration = new TlsConfiguration(); tlsConfiguration.setVerifyHostname(true); configuration.setTlsConfiguration(tlsConfiguration); final Registry<ConnectionSocketFactory> configuredRegistry; configuredRegistry = builder.using(configuration).createConfiguredRegistry(); assertThat(configuredRegistry).isNotNull(); final SSLConnectionSocketFactory socketFactory = (SSLConnectionSocketFactory) configuredRegistry.lookup("https"); assertThat(socketFactory).isNotNull(); final Field hostnameVerifierField = FieldUtils.getField(SSLConnectionSocketFactory.class, "hostnameVerifier", true); assertThat(hostnameVerifierField.get(socketFactory)).isInstanceOf(HostnameVerifier.class); } @Test public void createClientCanPassCustomVerifierToApacheBuilder() throws Exception { final HostnameVerifier customVerifier = (s, sslSession) -> false; assertThat(builder.using(customVerifier).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); final Field hostnameVerifierField = FieldUtils.getField(org.apache.http.impl.client.HttpClientBuilder.class, "hostnameVerifier", true); assertThat(hostnameVerifierField.get(apacheBuilder)).isSameAs(customVerifier); } @Test public void doesNotReuseConnectionsIfKeepAliveIsZero() throws Exception { configuration.setKeepAlive(Duration.seconds(0)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(spyHttpClientBuilderField("reuseStrategy", apacheBuilder)) .isInstanceOf(NoConnectionReuseStrategy.class); } @Test public void reusesConnectionsIfKeepAliveIsNonZero() throws Exception { configuration.setKeepAlive(Duration.seconds(1)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(spyHttpClientBuilderField("reuseStrategy", apacheBuilder)) .isInstanceOf(DefaultConnectionReuseStrategy.class); } @Test public void usesKeepAliveForPersistentConnections() throws Exception { configuration.setKeepAlive(Duration.seconds(1)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); final DefaultConnectionKeepAliveStrategy strategy = (DefaultConnectionKeepAliveStrategy) spyHttpClientBuilderField("keepAliveStrategy", apacheBuilder); final HttpContext context = mock(HttpContext.class); final HttpResponse response = mock(HttpResponse.class); when(response.headerIterator(HTTP.CONN_KEEP_ALIVE)).thenReturn(mock(HeaderIterator.class)); assertThat(strategy.getKeepAliveDuration(response, context)).isEqualTo(1000); } @Test public void usesDefaultForNonPersistentConnections() throws Exception { configuration.setKeepAlive(Duration.seconds(1)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); final Field field = FieldUtils.getField(httpClientBuilderClass, "keepAliveStrategy", true); final DefaultConnectionKeepAliveStrategy strategy = (DefaultConnectionKeepAliveStrategy) field.get(apacheBuilder); final HttpContext context = mock(HttpContext.class); final HttpResponse response = mock(HttpResponse.class); final HeaderIterator iterator = new BasicListHeaderIterator( ImmutableList.of(new BasicHeader(HttpHeaders.CONNECTION, "timeout=50")), HttpHeaders.CONNECTION ); when(response.headerIterator(HTTP.CONN_KEEP_ALIVE)).thenReturn(iterator); assertThat(strategy.getKeepAliveDuration(response, context)).isEqualTo(50000); } @Test public void ignoresCookiesByDefault() throws Exception { assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)).getCookieSpec()) .isEqualTo(CookieSpecs.IGNORE_COOKIES); } @Test public void usesBestMatchCookiePolicyIfCookiesAreEnabled() throws Exception { configuration.setCookiesEnabled(true); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)).getCookieSpec()) .isEqualTo(CookieSpecs.DEFAULT); } @Test public void setsTheSocketTimeout() throws Exception { configuration.setTimeout(Duration.milliseconds(500)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)).getSocketTimeout()) .isEqualTo(500); } @Test public void setsTheConnectTimeout() throws Exception { configuration.setConnectionTimeout(Duration.milliseconds(500)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)).getConnectTimeout()) .isEqualTo(500); } @Test public void setsTheConnectionRequestTimeout() throws Exception { configuration.setConnectionRequestTimeout(Duration.milliseconds(123)); assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)).getConnectionRequestTimeout()) .isEqualTo(123); } @Test public void disablesNaglesAlgorithm() throws Exception { assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(((SocketConfig) spyHttpClientBuilderField("defaultSocketConfig", apacheBuilder)).isTcpNoDelay()).isTrue(); } @Test public void disablesStaleConnectionCheck() throws Exception { assertThat(builder.using(configuration).createClient(apacheBuilder, connectionManager, "test")).isNotNull(); // It is fine to use the isStaleConnectionCheckEnabled deprecated API, as we are ensuring // that the builder creates a client that does not check for stale connections on each // request, which adds significant overhead. assertThat(((RequestConfig) spyHttpClientBuilderField("defaultRequestConfig", apacheBuilder)) .isStaleConnectionCheckEnabled()).isFalse(); } @Test public void usesTheDefaultRoutePlanner() throws Exception { final CloseableHttpClient httpClient = builder.using(configuration) .createClient(apacheBuilder, connectionManager, "test").getClient(); assertThat(httpClient).isNotNull(); assertThat(spyHttpClientBuilderField("routePlanner", apacheBuilder)).isNull(); assertThat(spyHttpClientField("routePlanner", httpClient)).isInstanceOf(DefaultRoutePlanner.class); } @Test public void usesACustomRoutePlanner() throws Exception { final HttpRoutePlanner routePlanner = new SystemDefaultRoutePlanner(new ProxySelector() { @Override public List<Proxy> select(URI uri) { return ImmutableList.of(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.52.1", 8080))); } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { } }); final CloseableHttpClient httpClient = builder.using(configuration).using(routePlanner) .createClient(apacheBuilder, connectionManager, "test").getClient(); assertThat(httpClient).isNotNull(); assertThat(spyHttpClientBuilderField("routePlanner", apacheBuilder)).isSameAs(routePlanner); assertThat(spyHttpClientField("routePlanner", httpClient)).isSameAs(routePlanner); } @Test public void usesACustomHttpRequestRetryHandler() throws Exception { final HttpRequestRetryHandler customHandler = (exception, executionCount, context) -> false; configuration.setRetries(1); assertThat(builder.using(configuration).using(customHandler) .createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(spyHttpClientBuilderField("retryHandler", apacheBuilder)).isSameAs(customHandler); } @Test public void usesCredentialsProvider() throws Exception { final CredentialsProvider credentialsProvider = new CredentialsProvider() { @Override public void setCredentials(AuthScope authscope, Credentials credentials) { } @Override public Credentials getCredentials(AuthScope authscope) { return null; } @Override public void clear() { } }; assertThat(builder.using(configuration).using(credentialsProvider) .createClient(apacheBuilder, connectionManager, "test")).isNotNull(); assertThat(spyHttpClientBuilderField("credentialsProvider", apacheBuilder)).isSameAs(credentialsProvider); } @Test public void usesProxy() throws Exception { HttpClientConfiguration config = new HttpClientConfiguration(); ProxyConfiguration proxy = new ProxyConfiguration("192.168.52.11", 8080); config.setProxyConfiguration(proxy); checkProxy(config, new HttpHost("dropwizard.io", 80), new HttpHost("192.168.52.11", 8080)); } @Test public void usesProxyWithoutPort() throws Exception { HttpClientConfiguration config = new HttpClientConfiguration(); ProxyConfiguration proxy = new ProxyConfiguration("192.168.52.11"); config.setProxyConfiguration(proxy); checkProxy(config, new HttpHost("dropwizard.io", 80), new HttpHost("192.168.52.11")); } @Test public void usesProxyWithAuth() throws Exception { HttpClientConfiguration config = new HttpClientConfiguration(); AuthConfiguration auth = new AuthConfiguration("secret", "stuff"); ProxyConfiguration proxy = new ProxyConfiguration("192.168.52.11", 8080, "http", auth); config.setProxyConfiguration(proxy); CloseableHttpClient httpClient = checkProxy(config, new HttpHost("dropwizard.io", 80), new HttpHost("192.168.52.11", 8080, "http")); CredentialsProvider credentialsProvider = (CredentialsProvider) FieldUtils.getField(httpClient.getClass(), "credentialsProvider", true) .get(httpClient); assertThat(credentialsProvider.getCredentials(new AuthScope("192.168.52.11", 8080))) .isEqualTo(new UsernamePasswordCredentials("secret", "stuff")); } @Test public void usesProxyWithNonProxyHosts() throws Exception { HttpClientConfiguration config = new HttpClientConfiguration(); ProxyConfiguration proxy = new ProxyConfiguration("192.168.52.11", 8080); proxy.setNonProxyHosts(ImmutableList.of("*.example.com")); config.setProxyConfiguration(proxy); checkProxy(config, new HttpHost("host.example.com", 80), null); } @Test public void usesProxyWithNonProxyHostsAndTargetDoesNotMatch() throws Exception { HttpClientConfiguration config = new HttpClientConfiguration(); ProxyConfiguration proxy = new ProxyConfiguration("192.168.52.11"); proxy.setNonProxyHosts(ImmutableList.of("*.example.com")); config.setProxyConfiguration(proxy); checkProxy(config, new HttpHost("dropwizard.io", 80), new HttpHost("192.168.52.11")); } @Test public void usesNoProxy() throws Exception { checkProxy(new HttpClientConfiguration(), new HttpHost("dropwizard.io", 80), null); } private CloseableHttpClient checkProxy(HttpClientConfiguration config, HttpHost target, HttpHost expectedProxy) throws Exception { CloseableHttpClient httpClient = builder.using(config).build("test"); HttpRoutePlanner routePlanner = (HttpRoutePlanner) FieldUtils.getField(httpClient.getClass(), "routePlanner", true).get(httpClient); HttpRoute route = routePlanner.determineRoute(target, new HttpGet(target.toURI()), new BasicHttpContext()); assertThat(route.getProxyHost()).isEqualTo(expectedProxy); assertThat(route.getTargetHost()).isEqualTo(target); assertThat(route.getHopCount()).isEqualTo(expectedProxy != null ? 2 : 1); return httpClient; } @Test public void setValidateAfterInactivityPeriodFromConfiguration() throws Exception { int validateAfterInactivityPeriod = 50000; configuration.setValidateAfterInactivityPeriod(Duration.milliseconds(validateAfterInactivityPeriod)); final ConfiguredCloseableHttpClient client = builder.using(configuration) .createClient(apacheBuilder, builder.configureConnectionManager(connectionManager), "test"); assertThat(client).isNotNull(); assertThat(spyHttpClientBuilderField("connManager", apacheBuilder)).isSameAs(connectionManager); verify(connectionManager).setValidateAfterInactivity(validateAfterInactivityPeriod); } @Test public void usesACustomHttpClientMetricNameStrategy() throws Exception { assertThat(builder.using(HttpClientMetricNameStrategies.HOST_AND_METHOD) .createClient(apacheBuilder, connectionManager, "test")) .isNotNull(); assertThat(FieldUtils.getField(InstrumentedHttpRequestExecutor.class, "metricNameStrategy", true) .get(spyHttpClientBuilderField("requestExec", apacheBuilder))) .isSameAs(HttpClientMetricNameStrategies.HOST_AND_METHOD); } @Test public void usesMethodOnlyHttpClientMetricNameStrategyByDefault() throws Exception { assertThat(builder.createClient(apacheBuilder, connectionManager, "test")) .isNotNull(); assertThat(FieldUtils.getField(InstrumentedHttpRequestExecutor.class, "metricNameStrategy", true) .get(spyHttpClientBuilderField("requestExec", apacheBuilder))) .isSameAs(HttpClientMetricNameStrategies.METHOD_ONLY); } @Test public void exposedConfigIsTheSameAsInternalToTheWrappedHttpClient() throws Exception { ConfiguredCloseableHttpClient client = builder.createClient(apacheBuilder, connectionManager, "test"); assertThat(client).isNotNull(); assertThat(spyHttpClientField("defaultConfig", client.getClient())).isEqualTo(client.getDefaultRequestConfig()); } @Test public void disablesContentCompression() throws Exception { ConfiguredCloseableHttpClient client = builder .disableContentCompression(true) .createClient(apacheBuilder, connectionManager, "test"); assertThat(client).isNotNull(); final Boolean contentCompressionDisabled = (Boolean) FieldUtils .getField(httpClientBuilderClass, "contentCompressionDisabled", true) .get(apacheBuilder); assertThat(contentCompressionDisabled).isTrue(); } @Test public void managedByEnvironment() throws Exception { final Environment environment = mock(Environment.class); when(environment.getName()).thenReturn("test-env"); when(environment.metrics()).thenReturn(new MetricRegistry()); final LifecycleEnvironment lifecycle = mock(LifecycleEnvironment.class); when(environment.lifecycle()).thenReturn(lifecycle); final CloseableHttpClient httpClient = mock(CloseableHttpClient.class); HttpClientBuilder httpClientBuilder = spy(new HttpClientBuilder(environment)); when(httpClientBuilder.buildWithDefaultRequestConfiguration("test-apache-client")) .thenReturn(new ConfiguredCloseableHttpClient(httpClient, RequestConfig.DEFAULT)); assertThat(httpClientBuilder.build("test-apache-client")).isSameAs(httpClient); // Verify that we registered the managed object final ArgumentCaptor<Managed> argumentCaptor = ArgumentCaptor.forClass(Managed.class); verify(lifecycle).manage(argumentCaptor.capture()); // Verify that the managed object actually stops the HTTP client final Managed managed = argumentCaptor.getValue(); managed.stop(); verify(httpClient).close(); } @Test public void usesACustomRedirectStrategy() throws Exception { RedirectStrategy neverFollowRedirectStrategy = new RedirectStrategy() { @Override public boolean isRedirected(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException { return false; } @Override public HttpUriRequest getRedirect(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) throws ProtocolException { return null; } }; ConfiguredCloseableHttpClient client = builder.using(neverFollowRedirectStrategy) .createClient(apacheBuilder, connectionManager, "test"); assertThat(client).isNotNull(); assertThat(spyHttpClientBuilderField("redirectStrategy", apacheBuilder)).isSameAs(neverFollowRedirectStrategy); } @Test public void usesDefaultHeaders() throws Exception { final ConfiguredCloseableHttpClient client = builder.using(ImmutableList.of(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "de"))) .createClient(apacheBuilder, connectionManager, "test"); assertThat(client).isNotNull(); @SuppressWarnings("unchecked") List<? extends Header> defaultHeaders = (List<? extends Header>) FieldUtils .getField(httpClientBuilderClass, "defaultHeaders", true) .get(apacheBuilder); assertThat(defaultHeaders).hasSize(1); final Header header = defaultHeaders.get(0); assertThat(header.getName()).isEqualTo(HttpHeaders.ACCEPT_LANGUAGE); assertThat(header.getValue()).isEqualTo("de"); } @Test public void usesHttpProcessor() throws Exception { HttpProcessor httpProcessor = mock(HttpProcessor.class); final ConfiguredCloseableHttpClient client = builder.using(httpProcessor) .createClient(apacheBuilder, connectionManager, "test"); assertThat(client).isNotNull(); assertThat(FieldUtils.getField(httpClientBuilderClass, "httpprocessor", true) .get(apacheBuilder)) .isSameAs(httpProcessor); } @Test public void allowsCustomBuilderConfiguration() throws Exception { CustomBuilder builder = new CustomBuilder(new MetricRegistry()); assertThat(builder.customized).isFalse(); ConfiguredCloseableHttpClient client = builder.createClient(apacheBuilder, connectionManager, "test"); assertThat(builder.customized).isTrue(); } private Object spyHttpClientBuilderField(final String fieldName, final Object obj) throws Exception { final Field field = FieldUtils.getField(httpClientBuilderClass, fieldName, true); return field.get(obj); } private Object spyHttpClientField(final String fieldName, final Object obj) throws Exception { final Field field = FieldUtils.getField(httpClientClass, fieldName, true); return field.get(obj); } }