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 <pwebb@vmware.com>
This commit is contained in:
committed by
Phillip Webb
parent
c22e76632c
commit
e6c37d698f
@@ -18,16 +18,22 @@ package org.springframework.boot.test.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
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;
|
||||
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;
|
||||
@@ -35,10 +41,12 @@ import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
|
||||
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
|
||||
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;
|
||||
|
||||
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.boot.web.client.RootUriTemplateHandler;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
@@ -138,8 +146,8 @@ public class TestRestTemplate {
|
||||
if (httpClientOptions != null) {
|
||||
ClientHttpRequestFactory requestFactory = builder.buildRequestFactory();
|
||||
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
|
||||
builder = builder
|
||||
.requestFactory(() -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions));
|
||||
builder = builder.requestFactory(
|
||||
(settings) -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions, settings));
|
||||
}
|
||||
}
|
||||
if (username != null || password != null) {
|
||||
@@ -1000,43 +1008,71 @@ public class TestRestTemplate {
|
||||
|
||||
private final boolean enableRedirects;
|
||||
|
||||
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions) {
|
||||
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions,
|
||||
ClientHttpRequestFactorySettings settings) {
|
||||
Set<HttpClientOption> options = new HashSet<>(Arrays.asList(httpClientOptions));
|
||||
this.cookieSpec = (options.contains(HttpClientOption.ENABLE_COOKIES) ? StandardCookieSpec.STRICT
|
||||
: StandardCookieSpec.IGNORE);
|
||||
this.enableRedirects = options.contains(HttpClientOption.ENABLE_REDIRECTS);
|
||||
if (options.contains(HttpClientOption.SSL)) {
|
||||
setHttpClient(createSslHttpClient());
|
||||
boolean ssl = options.contains(HttpClientOption.SSL);
|
||||
if (settings.readTimeout() != null || ssl) {
|
||||
setHttpClient(createHttpClient(settings.readTimeout(), ssl));
|
||||
}
|
||||
if (settings.connectTimeout() != null) {
|
||||
setConnectTimeout((int) settings.connectTimeout().toMillis());
|
||||
}
|
||||
if (settings.bufferRequestBody() != null) {
|
||||
setBufferRequestBody(settings.bufferRequestBody());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClient createSslHttpClient() {
|
||||
private HttpClient createHttpClient(Duration readTimeout, boolean ssl) {
|
||||
try {
|
||||
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
|
||||
.build();
|
||||
SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactoryBuilder.create()
|
||||
.setSslContext(sslContext).setTlsVersions(TLS.V_1_3, TLS.V_1_2).build();
|
||||
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
|
||||
.create().setSSLSocketFactory(socketFactory).build();
|
||||
|
||||
return HttpClients.custom().setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(getRequestConfig()).build();
|
||||
HttpClientBuilder builder = HttpClients.custom();
|
||||
builder.setConnectionManager(createConnectionManager(readTimeout, ssl));
|
||||
builder.setDefaultRequestConfig(createRequestConfig());
|
||||
return builder.build();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to create SSL HttpClient", ex);
|
||||
throw new IllegalStateException("Unable to create customized HttpClient", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private PoolingHttpClientConnectionManager createConnectionManager(Duration readTimeout, boolean ssl)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
|
||||
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
|
||||
if (ssl) {
|
||||
builder.setSSLSocketFactory(createSocketFactory());
|
||||
}
|
||||
if (readTimeout != null) {
|
||||
SocketConfig socketConfig = SocketConfig.custom()
|
||||
.setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
|
||||
builder.setDefaultSocketConfig(socketConfig);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private SSLConnectionSocketFactory createSocketFactory()
|
||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
|
||||
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
|
||||
.build();
|
||||
return SSLConnectionSocketFactoryBuilder.create().setSslContext(sslContext)
|
||||
.setTlsVersions(TLS.V_1_3, TLS.V_1_2).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setRequestConfig(getRequestConfig());
|
||||
context.setRequestConfig(createRequestConfig());
|
||||
return context;
|
||||
}
|
||||
|
||||
protected RequestConfig getRequestConfig() {
|
||||
return RequestConfig.custom().setCookieSpec(this.cookieSpec).setAuthenticationEnabled(false)
|
||||
.setRedirectsEnabled(this.enableRedirects).build();
|
||||
protected RequestConfig createRequestConfig() {
|
||||
RequestConfig.Builder builder = RequestConfig.custom();
|
||||
builder.setCookieSpec(this.cookieSpec);
|
||||
builder.setAuthenticationEnabled(false);
|
||||
builder.setRedirectsEnabled(this.enableRedirects);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestRestTemplateTests {
|
||||
TestRestTemplate template = new TestRestTemplate(HttpClientOption.ENABLE_REDIRECTS);
|
||||
CustomHttpComponentsClientHttpRequestFactory factory = (CustomHttpComponentsClientHttpRequestFactory) template
|
||||
.getRestTemplate().getRequestFactory();
|
||||
RequestConfig config = factory.getRequestConfig();
|
||||
RequestConfig config = factory.createRequestConfig();
|
||||
assertThat(config.isRedirectsEnabled()).isTrue();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user