Skip to content

Commit e6c37d6

Browse files
wilkinsonaphilwebb
andcommitted
Reinstate support for read timeouts with RestTemplateBuilder
Refactor the way `ClientHttpRequestFactory` instances are created in order to support setting read timeouts. Prior to this commit, the reflection based approach would call `setReadTimeout`. As of Spring Framework 6.0, the `HttpComponentsClientHttpRequestFactory` class no longer supports this approach. The timeout must be set on the `HttpClientConnectionManager` used in the `HttpClient` which can be passed in to the constructor. In order to support this approach, the `ClientHttpRequestFactory` can now be created using a `Function` rather than a `Supplier`. The function accepts a `ClientHttpRequestFactorySettings` which provides the timeout settings to apply. The `ClientHttpRequestFactories` utility class provides methods to create `ClientHttpRequestFactory` instances that respect the settings. Whenever possible, these are created without using reflection. Fixes gh-32857 Co-authored-by: Phillip Webb <[email protected]>
1 parent c22e766 commit e6c37d6

23 files changed

+1405
-544
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplate.java

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,35 @@
1818

1919
import java.io.IOException;
2020
import java.net.URI;
21+
import java.security.KeyManagementException;
22+
import java.security.KeyStoreException;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.time.Duration;
2125
import java.util.Arrays;
2226
import java.util.HashSet;
2327
import java.util.Map;
2428
import java.util.Set;
29+
import java.util.concurrent.TimeUnit;
2530

2631
import javax.net.ssl.SSLContext;
2732

2833
import org.apache.hc.client5.http.classic.HttpClient;
2934
import org.apache.hc.client5.http.config.RequestConfig;
3035
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
36+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
3137
import org.apache.hc.client5.http.impl.classic.HttpClients;
3238
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
3339
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
3440
import org.apache.hc.client5.http.protocol.HttpClientContext;
3541
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
3642
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
3743
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
44+
import org.apache.hc.core5.http.io.SocketConfig;
3845
import org.apache.hc.core5.http.protocol.HttpContext;
3946
import org.apache.hc.core5.http.ssl.TLS;
4047
import org.apache.hc.core5.ssl.SSLContextBuilder;
4148

49+
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
4250
import org.springframework.boot.web.client.RestTemplateBuilder;
4351
import org.springframework.boot.web.client.RootUriTemplateHandler;
4452
import org.springframework.core.ParameterizedTypeReference;
@@ -138,8 +146,8 @@ public TestRestTemplate(RestTemplateBuilder builder, String username, String pas
138146
if (httpClientOptions != null) {
139147
ClientHttpRequestFactory requestFactory = builder.buildRequestFactory();
140148
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
141-
builder = builder
142-
.requestFactory(() -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions));
149+
builder = builder.requestFactory(
150+
(settings) -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions, settings));
143151
}
144152
}
145153
if (username != null || password != null) {
@@ -1000,43 +1008,71 @@ protected static class CustomHttpComponentsClientHttpRequestFactory extends Http
10001008

10011009
private final boolean enableRedirects;
10021010

1003-
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions) {
1011+
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions,
1012+
ClientHttpRequestFactorySettings settings) {
10041013
Set<HttpClientOption> options = new HashSet<>(Arrays.asList(httpClientOptions));
10051014
this.cookieSpec = (options.contains(HttpClientOption.ENABLE_COOKIES) ? StandardCookieSpec.STRICT
10061015
: StandardCookieSpec.IGNORE);
10071016
this.enableRedirects = options.contains(HttpClientOption.ENABLE_REDIRECTS);
1008-
if (options.contains(HttpClientOption.SSL)) {
1009-
setHttpClient(createSslHttpClient());
1017+
boolean ssl = options.contains(HttpClientOption.SSL);
1018+
if (settings.readTimeout() != null || ssl) {
1019+
setHttpClient(createHttpClient(settings.readTimeout(), ssl));
1020+
}
1021+
if (settings.connectTimeout() != null) {
1022+
setConnectTimeout((int) settings.connectTimeout().toMillis());
1023+
}
1024+
if (settings.bufferRequestBody() != null) {
1025+
setBufferRequestBody(settings.bufferRequestBody());
10101026
}
10111027
}
10121028

1013-
private HttpClient createSslHttpClient() {
1029+
private HttpClient createHttpClient(Duration readTimeout, boolean ssl) {
10141030
try {
1015-
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
1016-
.build();
1017-
SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactoryBuilder.create()
1018-
.setSslContext(sslContext).setTlsVersions(TLS.V_1_3, TLS.V_1_2).build();
1019-
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
1020-
.create().setSSLSocketFactory(socketFactory).build();
1021-
1022-
return HttpClients.custom().setConnectionManager(connectionManager)
1023-
.setDefaultRequestConfig(getRequestConfig()).build();
1031+
HttpClientBuilder builder = HttpClients.custom();
1032+
builder.setConnectionManager(createConnectionManager(readTimeout, ssl));
1033+
builder.setDefaultRequestConfig(createRequestConfig());
1034+
return builder.build();
10241035
}
10251036
catch (Exception ex) {
1026-
throw new IllegalStateException("Unable to create SSL HttpClient", ex);
1037+
throw new IllegalStateException("Unable to create customized HttpClient", ex);
1038+
}
1039+
}
1040+
1041+
private PoolingHttpClientConnectionManager createConnectionManager(Duration readTimeout, boolean ssl)
1042+
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
1043+
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
1044+
if (ssl) {
1045+
builder.setSSLSocketFactory(createSocketFactory());
1046+
}
1047+
if (readTimeout != null) {
1048+
SocketConfig socketConfig = SocketConfig.custom()
1049+
.setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
1050+
builder.setDefaultSocketConfig(socketConfig);
10271051
}
1052+
return builder.build();
1053+
}
1054+
1055+
private SSLConnectionSocketFactory createSocketFactory()
1056+
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
1057+
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
1058+
.build();
1059+
return SSLConnectionSocketFactoryBuilder.create().setSslContext(sslContext)
1060+
.setTlsVersions(TLS.V_1_3, TLS.V_1_2).build();
10281061
}
10291062

10301063
@Override
10311064
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
10321065
HttpClientContext context = HttpClientContext.create();
1033-
context.setRequestConfig(getRequestConfig());
1066+
context.setRequestConfig(createRequestConfig());
10341067
return context;
10351068
}
10361069

1037-
protected RequestConfig getRequestConfig() {
1038-
return RequestConfig.custom().setCookieSpec(this.cookieSpec).setAuthenticationEnabled(false)
1039-
.setRedirectsEnabled(this.enableRedirects).build();
1070+
protected RequestConfig createRequestConfig() {
1071+
RequestConfig.Builder builder = RequestConfig.custom();
1072+
builder.setCookieSpec(this.cookieSpec);
1073+
builder.setAuthenticationEnabled(false);
1074+
builder.setRedirectsEnabled(this.enableRedirects);
1075+
return builder.build();
10401076
}
10411077

10421078
}

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ void options() {
134134
TestRestTemplate template = new TestRestTemplate(HttpClientOption.ENABLE_REDIRECTS);
135135
CustomHttpComponentsClientHttpRequestFactory factory = (CustomHttpComponentsClientHttpRequestFactory) template
136136
.getRestTemplate().getRequestFactory();
137-
RequestConfig config = factory.getRequestConfig();
137+
RequestConfig config = factory.createRequestConfig();
138138
assertThat(config.isRedirectsEnabled()).isTrue();
139139
}
140140

0 commit comments

Comments
 (0)