Replace calls to deprecated setConnectionTimeout method

Replace calls to the recently deprecated `setConnectionTimeout` method
of `HttpComponentsClientHttpRequestFactoryBuilder`.

This commit also introduces a `withConnectionConfigCustomizer` and
allows connection timeouts settings to be used.

Closes gh-48031
This commit is contained in:
Stéphane Nicoll
2025-11-10 15:32:05 +01:00
parent f1957a9740
commit 5a67f5707e
6 changed files with 128 additions and 61 deletions
@@ -72,6 +72,20 @@ class HttpClientAutoConfigurationTests {
});
}
@Test
void configuresClientHttpRequestFactorySettingsUsingDeprecatedProperties() {
this.contextRunner.withPropertyValues(sslPropertyValues().toArray(String[]::new))
.withPropertyValues("spring.http.client.redirects=dont-follow", "spring.http.client.connect-timeout=10s",
"spring.http.client.read-timeout=20s", "spring.http.client.ssl.bundle=test")
.run((context) -> {
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
assertThat(settings.redirects()).isEqualTo(Redirects.DONT_FOLLOW);
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(10));
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(20));
assertThat(settings.sslBundle().getKey().getAlias()).isEqualTo("alias1");
});
}
private List<String> sslPropertyValues() {
List<String> propertyValues = new ArrayList<>();
String location = "classpath:org/springframework/boot/autoconfigure/ssl/";
@@ -30,6 +30,7 @@ import java.util.function.UnaryOperator;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@@ -39,7 +40,6 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuil
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.ssl.SSLContextBuilder;
@@ -1082,18 +1082,15 @@ public class TestRestTemplate {
: StandardCookieSpec.IGNORE);
this.enableRedirects = settings.redirects() != Redirects.DONT_FOLLOW;
boolean ssl = HttpClientOption.SSL.isPresent(httpClientOptions);
if (settings.readTimeout() != null || ssl) {
setHttpClient(createHttpClient(settings.readTimeout(), ssl));
}
if (settings.connectTimeout() != null) {
setConnectTimeout((int) settings.connectTimeout().toMillis());
if (settings.connectTimeout() != null || settings.readTimeout() != null || ssl) {
setHttpClient(createHttpClient(settings.connectTimeout(), settings.readTimeout(), ssl));
}
}
private HttpClient createHttpClient(Duration readTimeout, boolean ssl) {
private HttpClient createHttpClient(Duration connectTimeout, Duration readTimeout, boolean ssl) {
try {
HttpClientBuilder builder = HttpClients.custom();
builder.setConnectionManager(createConnectionManager(readTimeout, ssl));
builder.setConnectionManager(createConnectionManager(connectTimeout, readTimeout, ssl));
builder.setDefaultRequestConfig(createRequestConfig());
return builder.build();
}
@@ -1102,18 +1099,23 @@ public class TestRestTemplate {
}
}
private PoolingHttpClientConnectionManager createConnectionManager(Duration readTimeout, boolean ssl)
private PoolingHttpClientConnectionManager createConnectionManager(Duration connectTimeout,
Duration readTimeout, boolean ssl)
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
if (connectTimeout != null || readTimeout != null) {
ConnectionConfig.Builder connectionConfig = ConnectionConfig.custom();
if (connectTimeout != null) {
connectionConfig.setConnectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS);
}
if (readTimeout != null) {
connectionConfig.setSocketTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS);
}
builder.setDefaultConnectionConfig(connectionConfig.build());
}
if (ssl) {
builder.setTlsSocketStrategy(createTlsSocketStrategy());
}
if (readTimeout != null) {
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS)
.build();
builder.setDefaultSocketConfig(socketConfig);
}
return builder.build();
}
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@@ -65,13 +66,15 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
private final Consumer<SocketConfig.Builder> socketConfigCustomizer;
private final Consumer<ConnectionConfig.Builder> connectionConfigCustomizer;
private final Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer;
private final Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory;
HttpComponentsClientHttpRequestFactoryBuilder() {
this(Collections.emptyList(), emptyCustomizer(), emptyCustomizer(), emptyCustomizer(), emptyCustomizer(),
HttpComponentsClientHttpRequestFactoryBuilder::createTlsSocketStrategy);
emptyCustomizer(), HttpComponentsClientHttpRequestFactoryBuilder::createTlsSocketStrategy);
}
private static TlsSocketStrategy createTlsSocketStrategy(SslBundle sslBundle) {
@@ -85,12 +88,14 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Consumer<HttpClientBuilder> httpClientCustomizer,
Consumer<PoolingHttpClientConnectionManagerBuilder> connectionManagerCustomizer,
Consumer<SocketConfig.Builder> socketConfigCustomizer,
Consumer<ConnectionConfig.Builder> connectionConfigCustomizer,
Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer,
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
super(customizers);
this.httpClientCustomizer = httpClientCustomizer;
this.connectionManagerCustomizer = connectionManagerCustomizer;
this.socketConfigCustomizer = socketConfigCustomizer;
this.connectionConfigCustomizer = connectionConfigCustomizer;
this.defaultRequestConfigCustomizer = defaultRequestConfigCustomizer;
this.tlsSocketStrategyFactory = tlsSocketStrategyFactory;
}
@@ -100,7 +105,7 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Consumer<HttpComponentsClientHttpRequestFactory> customizer) {
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizer),
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
@Override
@@ -108,7 +113,7 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Collection<Consumer<HttpComponentsClientHttpRequestFactory>> customizers) {
return new HttpComponentsClientHttpRequestFactoryBuilder(mergedCustomizers(customizers),
this.httpClientCustomizer, this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
@@ -122,7 +127,8 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Assert.notNull(httpClientCustomizer, "'httpClientCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(),
this.httpClientCustomizer.andThen(httpClientCustomizer), this.connectionManagerCustomizer,
this.socketConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
this.socketConfigCustomizer, this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer,
this.tlsSocketStrategyFactory);
}
/**
@@ -137,7 +143,7 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Assert.notNull(connectionManagerCustomizer, "'connectionManagerCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer.andThen(connectionManagerCustomizer), this.socketConfigCustomizer,
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
@@ -152,6 +158,23 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Assert.notNull(socketConfigCustomizer, "'socketConfigCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer.andThen(socketConfigCustomizer),
this.connectionConfigCustomizer, this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies
* additional customization to the underlying
* {@link org.apache.hc.client5.http.config.ConnectionConfig.Builder}.
* @param connectionConfigCustomizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} instance
* @since 3.4.12
*/
public HttpComponentsClientHttpRequestFactoryBuilder withConnectionConfigCustomizer(
Consumer<ConnectionConfig.Builder> connectionConfigCustomizer) {
Assert.notNull(connectionConfigCustomizer, "'connectionConfigCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.connectionConfigCustomizer.andThen(connectionConfigCustomizer),
this.defaultRequestConfigCustomizer, this.tlsSocketStrategyFactory);
}
@@ -166,8 +189,8 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Function<SslBundle, TlsSocketStrategy> tlsSocketStrategyFactory) {
Assert.notNull(tlsSocketStrategyFactory, "'tlsSocketStrategyFactory' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer, this.defaultRequestConfigCustomizer,
tlsSocketStrategyFactory);
this.connectionManagerCustomizer, this.socketConfigCustomizer, this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer, tlsSocketStrategyFactory);
}
/**
@@ -182,7 +205,7 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
Consumer<RequestConfig.Builder> defaultRequestConfigCustomizer) {
Assert.notNull(defaultRequestConfigCustomizer, "'defaultRequestConfigCustomizer' must not be null");
return new HttpComponentsClientHttpRequestFactoryBuilder(getCustomizers(), this.httpClientCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer,
this.connectionManagerCustomizer, this.socketConfigCustomizer, this.connectionConfigCustomizer,
this.defaultRequestConfigCustomizer.andThen(defaultRequestConfigCustomizer),
this.tlsSocketStrategyFactory);
}
@@ -191,10 +214,7 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(
ClientHttpRequestFactorySettings settings) {
HttpClient httpClient = createHttpClient(settings);
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(factory::setConnectTimeout);
return factory;
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
private HttpClient createHttpClient(ClientHttpRequestFactorySettings settings) {
@@ -218,19 +238,29 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create()
.useSystemProperties();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
builder.setDefaultSocketConfig(createSocketConfig(settings));
builder.setDefaultSocketConfig(createSocketConfig());
builder.setDefaultConnectionConfig(createConnectionConfig(settings));
map.from(settings::sslBundle).as(this.tlsSocketStrategyFactory).to(builder::setTlsSocketStrategy);
this.connectionManagerCustomizer.accept(builder);
return builder.build();
}
private SocketConfig createSocketConfig(ClientHttpRequestFactorySettings settings) {
private SocketConfig createSocketConfig() {
SocketConfig.Builder builder = SocketConfig.custom();
this.socketConfigCustomizer.accept(builder);
return builder.build();
}
private ConnectionConfig createConnectionConfig(ClientHttpRequestFactorySettings settings) {
ConnectionConfig.Builder builder = ConnectionConfig.custom();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout)
.as(Duration::toMillis)
.to((timeout) -> builder.setConnectTimeout(timeout, TimeUnit.MILLISECONDS));
map.from(settings::readTimeout)
.asInt(Duration::toMillis)
.to((timeout) -> builder.setSoTimeout(timeout, TimeUnit.MILLISECONDS));
this.socketConfigCustomizer.accept(builder);
.to((timeout) -> builder.setSocketTimeout(timeout, TimeUnit.MILLISECONDS));
this.connectionConfigCustomizer.accept(builder);
return builder.build();
}
@@ -21,8 +21,9 @@ import java.util.List;
import java.util.function.Function;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.HttpClient;
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.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
@@ -94,18 +95,22 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
return getConnectorConfig(requestFactory).getConnectTimeout().toMilliseconds();
}
@Override
@SuppressWarnings("unchecked")
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
SocketConfig socketConfig = ((Resolver<HttpRoute, SocketConfig>) ReflectionTestUtils.getField(connectionManager,
"socketConfigResolver"))
.resolve(null);
return socketConfig.getSoTimeout().toMilliseconds();
return getConnectorConfig(requestFactory).getSocketTimeout().toMilliseconds();
}
@SuppressWarnings("unchecked")
private ConnectionConfig getConnectorConfig(HttpComponentsClientHttpRequestFactory requestFactory) {
CloseableHttpClient httpClient = (CloseableHttpClient) ReflectionTestUtils.getField(requestFactory,
"httpClient");
Object manager = ReflectionTestUtils.getField(httpClient, "connManager");
ConnectionConfig connectorConfig = ((Resolver<HttpRoute, ConnectionConfig>) ReflectionTestUtils
.getField(manager, "connectionConfigResolver")).resolve(null);
return connectorConfig;
}
}
@@ -17,9 +17,9 @@
package org.springframework.boot.web.client;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.io.SocketConfig;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
@@ -38,22 +38,6 @@ class ClientHttpRequestFactoriesHttpComponentsTests
super(HttpComponentsClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
@SuppressWarnings("unchecked")
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
SocketConfig socketConfig = ((Resolver<HttpRoute, SocketConfig>) ReflectionTestUtils.getField(connectionManager,
"socketConfigResolver"))
.resolve(null);
return socketConfig.getSoTimeout().toMilliseconds();
}
@Override
protected boolean supportsSettingConnectTimeout() {
return true;
@@ -61,7 +45,27 @@ class ClientHttpRequestFactoriesHttpComponentsTests
@Override
protected boolean supportsSettingReadTimeout() {
return false;
return true;
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return getConnectorConfig(requestFactory).getConnectTimeout().toMilliseconds();
}
@Override
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return getConnectorConfig(requestFactory).getSocketTimeout().toMilliseconds();
}
@SuppressWarnings("unchecked")
private ConnectionConfig getConnectorConfig(HttpComponentsClientHttpRequestFactory requestFactory) {
CloseableHttpClient httpClient = (CloseableHttpClient) ReflectionTestUtils.getField(requestFactory,
"httpClient");
Object manager = ReflectionTestUtils.getField(httpClient, "connManager");
ConnectionConfig connectorConfig = ((Resolver<HttpRoute, ConnectionConfig>) ReflectionTestUtils
.getField(manager, "connectionConfigResolver")).resolve(null);
return connectorConfig;
}
}
@@ -25,6 +25,11 @@ import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.util.Timeout;
import org.assertj.core.extractor.Extractors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
@@ -348,11 +353,18 @@ class RestTemplateBuilderTests {
}
@Test
@SuppressWarnings("unchecked")
void requestFactorySettingsAppliesSettings() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofSeconds(1));
.withConnectTimeout(Duration.ofSeconds(1))
.withReadTimeout(Duration.ofSeconds(2));
RestTemplate template = this.builder.requestFactorySettings(settings).build();
assertThat(template.getRequestFactory()).extracting("connectTimeout").isEqualTo(1000L);
Resolver<HttpRoute, ConnectionConfig> resolver = (Resolver<HttpRoute, ConnectionConfig>) Extractors
.byName("httpClient.connManager.connectionConfigResolver")
.apply(template.getRequestFactory());
ConnectionConfig config = resolver.resolve(mock());
assertThat(config.getConnectTimeout()).isEqualTo(Timeout.of(Duration.ofSeconds(1)));
assertThat(config.getSocketTimeout()).isEqualTo(Timeout.of(Duration.ofSeconds(2)));
}
@Test