diff --git a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java index 301feb2d8..7572d61d1 100644 --- a/src/main/java/com/adyen/httpclient/AdyenHttpClient.java +++ b/src/main/java/com/adyen/httpclient/AdyenHttpClient.java @@ -56,7 +56,9 @@ import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.http.HttpHost; @@ -91,9 +93,33 @@ public class AdyenHttpClient implements ClientInterface { private static final String CHARSET = "UTF-8"; private Proxy proxy; + private final PoolingHttpClientConnectionManager sharedConnectionManager; private volatile CloseableHttpClient sharedHttpClient; private final Object lock = new Object(); + public AdyenHttpClient() { + this(null); + } + + /** + * Creates an AdyenHttpClient with a shared connection manager. + *

+ * Note: When using a shared connection manager: + *

+ *

+ * + * @param connectionManager the shared connection manager to use + */ + public AdyenHttpClient(PoolingHttpClientConnectionManager connectionManager) { + this.sharedConnectionManager = connectionManager; + } + /** * Returns the proxy configured for this HTTP client. * @@ -372,7 +398,12 @@ private CloseableHttpClient createHttpClientWithSocketFactory( config.getConnectionRequestTimeoutMillis(), TimeUnit.MILLISECONDS) .setDefaultKeepAlive(config.getDefaultKeepAliveMillis(), TimeUnit.MILLISECONDS) .build(); - ConnectionConfig connectionConfig = + + HttpClientBuilder clientBuilder = HttpClients.custom(); + if (sharedConnectionManager != null) { + clientBuilder = clientBuilder.setConnectionManager(sharedConnectionManager).setConnectionManagerShared(true); + } else { + ConnectionConfig connectionConfig = ConnectionConfig.custom() .setConnectTimeout(config.getConnectionTimeoutMillis(), TimeUnit.MILLISECONDS) // socketTimeout acts as an OS-level safety net for stalled reads; @@ -381,12 +412,14 @@ private CloseableHttpClient createHttpClientWithSocketFactory( // fires first. .setSocketTimeout(config.getReadTimeoutMillis(), TimeUnit.MILLISECONDS) .build(); - return HttpClients.custom() - .setConnectionManager( + clientBuilder = clientBuilder.setConnectionManager( PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory(socketFactory) .setDefaultConnectionConfig(connectionConfig) - .build()) + .build()); + } + + return clientBuilder .setDefaultRequestConfig(defaultRequestConfig) .setRedirectStrategy(new AdyenCustomRedirectStrategy()) .build(); diff --git a/src/test/java/com/adyen/httpclient/ClientTest.java b/src/test/java/com/adyen/httpclient/ClientTest.java index 919ff9aad..a2c164842 100644 --- a/src/test/java/com/adyen/httpclient/ClientTest.java +++ b/src/test/java/com/adyen/httpclient/ClientTest.java @@ -1,6 +1,9 @@ package com.adyen.httpclient; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import com.adyen.BaseTest; import com.adyen.Client; @@ -17,6 +20,7 @@ import org.apache.hc.client5.http.config.Configurable; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.core5.http.Header; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -177,6 +181,39 @@ public void testDefaultRequestConfigTimeoutsWithConfigDefaults() throws Exceptio } } + @Test + public void testInjectedConnectionManagerAppliesDefaultRequestConfig() throws Exception { + PoolingHttpClientConnectionManager connectionManager = + mock(PoolingHttpClientConnectionManager.class); + Config config = new Config(); + config.setReadTimeoutMillis(15000); + config.setConnectionRequestTimeoutMillis(30000); + config.setDefaultKeepAliveMillis(40000); + + try (AdyenHttpClient adyenHttpClient = new AdyenHttpClient(connectionManager); + CloseableHttpClient httpClient = adyenHttpClient.createCloseableHttpClient(config)) { + assertInstanceOf(Configurable.class, httpClient); + RequestConfig defaultConfig = ((Configurable) httpClient).getConfig(); + assertNotNull(defaultConfig); + assertEquals(15000, defaultConfig.getResponseTimeout().toMilliseconds()); + assertEquals(30000, defaultConfig.getConnectionRequestTimeout().toMilliseconds()); + } + } + + @Test + public void testClosingHttpClientDoesNotCloseInjectedConnectionManager() throws Exception { + PoolingHttpClientConnectionManager connectionManager = + mock(PoolingHttpClientConnectionManager.class); + + try (AdyenHttpClient adyenHttpClient = new AdyenHttpClient(connectionManager); + CloseableHttpClient httpClient = adyenHttpClient.createCloseableHttpClient(new Config())) { + assertNotNull(httpClient); + adyenHttpClient.close(); + } + + verify(connectionManager, never()).close(); + } + @Test public void testPerRequestConfigIncludesAllTimeouts() throws Exception { AdyenHttpClient adyenHttpClient = new AdyenHttpClient();