Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions src/main/java/com/adyen/httpclient/AdyenHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* <b>Note:</b> When using a shared connection manager:
* <ul>
* <li>Any custom {@link SSLContext} or {@link HostnameVerifier} configured in {@link Config}
* will be ignored, as the connection manager's own socket factory registry is used.</li>
* <li>Connection-level configurations (such as {@code connectTimeout} and {@code socketTimeout})
* must be pre-configured on the shared connection manager, as the timeouts from {@link Config}
* will only be applied at the request level (via {@link RequestConfig}).</li>
* </ul>
* </p>
*
* @param connectionManager the shared connection manager to use
*/
public AdyenHttpClient(PoolingHttpClientConnectionManager connectionManager) {
this.sharedConnectionManager = connectionManager;
}

/**
* Returns the proxy configured for this HTTP client.
*
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/com/adyen/httpclient/ClientTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down