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>
pull/32880/head
Andy Wilkinson 2 years ago 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.io.IOException;
import java.net.URI; 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.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec; 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.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; 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.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; 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.protocol.HttpContext;
import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.ssl.SSLContextBuilder; 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.RestTemplateBuilder;
import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
@ -138,8 +146,8 @@ public class TestRestTemplate {
if (httpClientOptions != null) { if (httpClientOptions != null) {
ClientHttpRequestFactory requestFactory = builder.buildRequestFactory(); ClientHttpRequestFactory requestFactory = builder.buildRequestFactory();
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) { if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
builder = builder builder = builder.requestFactory(
.requestFactory(() -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions)); (settings) -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions, settings));
} }
} }
if (username != null || password != null) { if (username != null || password != null) {
@ -1000,43 +1008,71 @@ public class TestRestTemplate {
private final boolean enableRedirects; private final boolean enableRedirects;
public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions) { public CustomHttpComponentsClientHttpRequestFactory(HttpClientOption[] httpClientOptions,
ClientHttpRequestFactorySettings settings) {
Set<HttpClientOption> options = new HashSet<>(Arrays.asList(httpClientOptions)); Set<HttpClientOption> options = new HashSet<>(Arrays.asList(httpClientOptions));
this.cookieSpec = (options.contains(HttpClientOption.ENABLE_COOKIES) ? StandardCookieSpec.STRICT this.cookieSpec = (options.contains(HttpClientOption.ENABLE_COOKIES) ? StandardCookieSpec.STRICT
: StandardCookieSpec.IGNORE); : StandardCookieSpec.IGNORE);
this.enableRedirects = options.contains(HttpClientOption.ENABLE_REDIRECTS); this.enableRedirects = options.contains(HttpClientOption.ENABLE_REDIRECTS);
if (options.contains(HttpClientOption.SSL)) { boolean ssl = options.contains(HttpClientOption.SSL);
setHttpClient(createSslHttpClient()); 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 { try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()) HttpClientBuilder builder = HttpClients.custom();
.build(); builder.setConnectionManager(createConnectionManager(readTimeout, ssl));
SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactoryBuilder.create() builder.setDefaultRequestConfig(createRequestConfig());
.setSslContext(sslContext).setTlsVersions(TLS.V_1_3, TLS.V_1_2).build(); return builder.build();
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
.create().setSSLSocketFactory(socketFactory).build();
return HttpClients.custom().setConnectionManager(connectionManager)
.setDefaultRequestConfig(getRequestConfig()).build();
} }
catch (Exception ex) { 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 @Override
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
HttpClientContext context = HttpClientContext.create(); HttpClientContext context = HttpClientContext.create();
context.setRequestConfig(getRequestConfig()); context.setRequestConfig(createRequestConfig());
return context; return context;
} }
protected RequestConfig getRequestConfig() { protected RequestConfig createRequestConfig() {
return RequestConfig.custom().setCookieSpec(this.cookieSpec).setAuthenticationEnabled(false) RequestConfig.Builder builder = RequestConfig.custom();
.setRedirectsEnabled(this.enableRedirects).build(); 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); TestRestTemplate template = new TestRestTemplate(HttpClientOption.ENABLE_REDIRECTS);
CustomHttpComponentsClientHttpRequestFactory factory = (CustomHttpComponentsClientHttpRequestFactory) template CustomHttpComponentsClientHttpRequestFactory factory = (CustomHttpComponentsClientHttpRequestFactory) template
.getRestTemplate().getRequestFactory(); .getRestTemplate().getRequestFactory();
RequestConfig config = factory.getRequestConfig(); RequestConfig config = factory.createRequestConfig();
assertThat(config.isRedirectsEnabled()).isTrue(); assertThat(config.isRedirectsEnabled()).isTrue();
} }

@ -0,0 +1,264 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Utility class that can be used to create {@link ClientHttpRequestFactory} instances
* configured using given {@link ClientHttpRequestFactorySettings}.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.0.0
*/
public final class ClientHttpRequestFactories {
static final String APACHE_HTTP_CLIENT_CLASS = "org.apache.hc.client5.http.impl.classic.HttpClients";
private static final boolean APACHE_HTTP_CLIENT_PRESENT = ClassUtils.isPresent(APACHE_HTTP_CLIENT_CLASS, null);
static final String OKHTTP_CLIENT_CLASS = "okhttp3.OkHttpClient";
private static final boolean OKHTTP_CLIENT_PRESENT = ClassUtils.isPresent(OKHTTP_CLIENT_CLASS, null);
private ClientHttpRequestFactories() {
}
/**
* Return a new {@link ClientHttpRequestFactory} instance using the most appropriate
* implementation.
* @param settings the settings to apply
* @return a new {@link ClientHttpRequestFactory}
*/
public static ClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) {
Assert.notNull(settings, "Settings must not be null");
if (APACHE_HTTP_CLIENT_PRESENT) {
return HttpComponents.get(settings);
}
if (OKHTTP_CLIENT_PRESENT) {
return OkHttp.get(settings);
}
return Simple.get(settings);
}
/**
* Return a new {@link ClientHttpRequestFactory} of the given type, applying
* {@link ClientHttpRequestFactorySettings} using reflection if necessary.
* @param <T> the {@link ClientHttpRequestFactory} type
* @param requestFactoryType the {@link ClientHttpRequestFactory} type
* @param settings the settings to apply
* @return a new {@link ClientHttpRequestFactory} instance
*/
@SuppressWarnings("unchecked")
public static <T extends ClientHttpRequestFactory> T get(Class<T> requestFactoryType,
ClientHttpRequestFactorySettings settings) {
Assert.notNull(settings, "Settings must not be null");
if (requestFactoryType == ClientHttpRequestFactory.class) {
return (T) get(settings);
}
if (requestFactoryType == HttpComponentsClientHttpRequestFactory.class) {
return (T) HttpComponents.get(settings);
}
if (requestFactoryType == OkHttp3ClientHttpRequestFactory.class) {
return (T) OkHttp.get(settings);
}
if (requestFactoryType == SimpleClientHttpRequestFactory.class) {
return (T) Simple.get(settings);
}
return get(() -> createRequestFactory(requestFactoryType), settings);
}
/**
* Return a new {@link ClientHttpRequestFactory} from the given supplier, applying
* {@link ClientHttpRequestFactorySettings} using reflection.
* @param <T> the {@link ClientHttpRequestFactory} type
* @param requestFactorySupplier the {@link ClientHttpRequestFactory} supplier
* @param settings the settings to apply
* @return a new {@link ClientHttpRequestFactory} instance
*/
public static <T extends ClientHttpRequestFactory> T get(Supplier<T> requestFactorySupplier,
ClientHttpRequestFactorySettings settings) {
return Reflective.get(requestFactorySupplier, settings);
}
private static <T extends ClientHttpRequestFactory> T createRequestFactory(Class<T> requestFactory) {
try {
Constructor<T> constructor = requestFactory.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/**
* Support for {@link HttpComponentsClientHttpRequestFactory}.
*/
static class HttpComponents {
static HttpComponentsClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) {
HttpComponentsClientHttpRequestFactory requestFactory = createRequestFactory(settings.readTimeout());
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
map.from(settings::bufferRequestBody).to(requestFactory::setBufferRequestBody);
return requestFactory;
}
private static HttpComponentsClientHttpRequestFactory createRequestFactory(Duration readTimeout) {
return (readTimeout != null) ? new HttpComponentsClientHttpRequestFactory(createHttpClient(readTimeout))
: new HttpComponentsClientHttpRequestFactory();
}
private static HttpClient createHttpClient(Duration readTimeout) {
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout((int) readTimeout.toMillis(), TimeUnit.MILLISECONDS).build();
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultSocketConfig(socketConfig).build();
return HttpClientBuilder.create().setConnectionManager(connectionManager).build();
}
}
/**
* Support for {@link OkHttp3ClientHttpRequestFactory}.
*/
static class OkHttp {
static OkHttp3ClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) {
Assert.state(settings.bufferRequestBody() == null,
() -> "OkHttp3ClientHttpRequestFactory does not support request body buffering");
OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
return requestFactory;
}
}
/**
* Support for {@link SimpleClientHttpRequestFactory}.
*/
static class Simple {
static SimpleClientHttpRequestFactory get(ClientHttpRequestFactorySettings settings) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::readTimeout).asInt(Duration::toMillis).to(requestFactory::setReadTimeout);
map.from(settings::connectTimeout).asInt(Duration::toMillis).to(requestFactory::setConnectTimeout);
map.from(settings::bufferRequestBody).to(requestFactory::setBufferRequestBody);
return requestFactory;
}
}
/**
* Support for reflective configuration of an unknown {@link ClientHttpRequestFactory}
* implementation.
*/
static class Reflective {
static <T extends ClientHttpRequestFactory> T get(Supplier<T> requestFactorySupplier,
ClientHttpRequestFactorySettings settings) {
T requestFactory = requestFactorySupplier.get();
configure(requestFactory, settings);
return requestFactory;
}
private static void configure(ClientHttpRequestFactory requestFactory,
ClientHttpRequestFactorySettings settings) {
ClientHttpRequestFactory unwrapped = unwrapRequestFactoryIfNecessary(requestFactory);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(settings::connectTimeout).to((connectTimeout) -> setConnectTimeout(unwrapped, connectTimeout));
map.from(settings::readTimeout).to((readTimeout) -> setReadTimeout(unwrapped, readTimeout));
map.from(settings::bufferRequestBody)
.to((bufferRequestBody) -> setBufferRequestBody(unwrapped, bufferRequestBody));
}
private static ClientHttpRequestFactory unwrapRequestFactoryIfNecessary(
ClientHttpRequestFactory requestFactory) {
if (!(requestFactory instanceof AbstractClientHttpRequestFactoryWrapper)) {
return requestFactory;
}
Field field = ReflectionUtils.findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory");
ReflectionUtils.makeAccessible(field);
ClientHttpRequestFactory unwrappedRequestFactory = requestFactory;
while (unwrappedRequestFactory instanceof AbstractClientHttpRequestFactoryWrapper) {
unwrappedRequestFactory = (ClientHttpRequestFactory) ReflectionUtils.getField(field,
unwrappedRequestFactory);
}
return unwrappedRequestFactory;
}
private static void setConnectTimeout(ClientHttpRequestFactory factory, Duration connectTimeout) {
Method method = findMethod(factory, "setConnectTimeout", int.class);
int timeout = Math.toIntExact(connectTimeout.toMillis());
invoke(factory, method, timeout);
}
private static void setReadTimeout(ClientHttpRequestFactory factory, Duration readTimeout) {
Method method = findMethod(factory, "setReadTimeout", int.class);
int timeout = Math.toIntExact(readTimeout.toMillis());
invoke(factory, method, timeout);
}
private static void setBufferRequestBody(ClientHttpRequestFactory factory, boolean bufferRequestBody) {
Method method = findMethod(factory, "setBufferRequestBody", boolean.class);
invoke(factory, method, bufferRequestBody);
}
private static Method findMethod(ClientHttpRequestFactory requestFactory, String methodName,
Class<?>... parameters) {
Method method = ReflectionUtils.findMethod(requestFactory.getClass(), methodName, parameters);
Assert.state(method != null, () -> "Request factory %s does not have a suitable %s method"
.formatted(requestFactory.getClass().getName(), methodName));
Assert.state(!method.isAnnotationPresent(Deprecated.class),
() -> "Request factory %s has the %s method marked as deprecated"
.formatted(requestFactory.getClass().getName(), methodName));
return method;
}
private static void invoke(ClientHttpRequestFactory requestFactory, Method method, Object... parameters) {
ReflectionUtils.invokeMethod(method, requestFactory, parameters);
}
}
}

@ -0,0 +1,92 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.util.function.Consumer;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link RuntimeHintsRegistrar} for {@link ClientHttpRequestFactories}.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (ClassUtils.isPresent("org.springframework.http.client.ClientHttpRequestFactory", classLoader)) {
registerHints(hints.reflection(), classLoader);
}
}
private void registerHints(ReflectionHints hints, ClassLoader classLoader) {
hints.registerField(findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory"));
if (ClassUtils.isPresent(ClientHttpRequestFactories.APACHE_HTTP_CLIENT_CLASS, classLoader)) {
registerReflectionHints(hints, HttpComponentsClientHttpRequestFactory.class, this::onReachableHttpClient);
}
if (ClassUtils.isPresent(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS, classLoader)) {
registerReflectionHints(hints, OkHttp3ClientHttpRequestFactory.class, this::onReachableOkHttpClient);
}
registerReflectionHints(hints, SimpleClientHttpRequestFactory.class, this::onReachableHttpUrlConnection);
}
private void onReachableHttpUrlConnection(TypeHint.Builder typeHint) {
typeHint.onReachableType(HttpURLConnection.class);
}
private void onReachableHttpClient(TypeHint.Builder typeHint) {
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.APACHE_HTTP_CLIENT_CLASS));
}
private void onReachableOkHttpClient(TypeHint.Builder typeHint) {
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.OKHTTP_CLIENT_CLASS));
}
private void registerReflectionHints(ReflectionHints hints,
Class<? extends ClientHttpRequestFactory> requestFactoryType, Consumer<TypeHint.Builder> hintCustomizer) {
hints.registerType(requestFactoryType, (typeHint) -> {
typeHint.withMethod("setConnectTimeout", TypeReference.listOf(int.class), ExecutableMode.INVOKE);
typeHint.withMethod("setReadTimeout", TypeReference.listOf(int.class), ExecutableMode.INVOKE);
typeHint.withMethod("setBufferRequestBody", TypeReference.listOf(boolean.class), ExecutableMode.INVOKE);
hintCustomizer.accept(typeHint);
});
}
private Field findField(Class<?> type, String name) {
Field field = ReflectionUtils.findField(type, name);
Assert.state(field != null, () -> "Unable to find field '%s' on %s".formatted(type.getName(), name));
return field;
}
}

@ -0,0 +1,74 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.time.Duration;
import org.springframework.http.client.ClientHttpRequestFactory;
/**
* Settings that can be applied when creating a {@link ClientHttpRequestFactory}.
* @param connectTimeout the connect timeout
* @param readTimeout the read timeout
* @param bufferRequestBody if request body buffering is used
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.0.0
* @see ClientHttpRequestFactories
*/
public record ClientHttpRequestFactorySettings(Duration connectTimeout, Duration readTimeout,
Boolean bufferRequestBody) {
/**
* Use defaults for the {@link ClientHttpRequestFactory} which can differ depending on
* the implementation.
*/
public static final ClientHttpRequestFactorySettings DEFAULTS = new ClientHttpRequestFactorySettings(null, null,
null);
/**
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated
* connect timeout setting .
* @param connectTimeout the new connect timeout setting
* @return a new {@link ClientHttpRequestFactorySettings} instance
*/
public ClientHttpRequestFactorySettings withConnectTimeout(Duration connectTimeout) {
return new ClientHttpRequestFactorySettings(connectTimeout, this.readTimeout, this.bufferRequestBody);
}
/**
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated read
* timeout setting.
* @param readTimeout the new read timeout setting
* @return a new {@link ClientHttpRequestFactorySettings} instance
*/
public ClientHttpRequestFactorySettings withReadTimeout(Duration readTimeout) {
return new ClientHttpRequestFactorySettings(this.connectTimeout, readTimeout, this.bufferRequestBody);
}
/**
* Return a new {@link ClientHttpRequestFactorySettings} instance with an updated
* buffer request body setting.
* @param bufferRequestBody the new buffer request body setting
* @return a new {@link ClientHttpRequestFactorySettings} instance
*/
public ClientHttpRequestFactorySettings withBufferRequestBody(Boolean bufferRequestBody) {
return new ClientHttpRequestFactorySettings(this.connectTimeout, this.readTimeout, bufferRequestBody);
}
}

@ -16,17 +16,9 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.TypeReference;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ClassUtils;
/** /**
* A supplier for {@link ClientHttpRequestFactory} that detects the preferred candidate * A supplier for {@link ClientHttpRequestFactory} that detects the preferred candidate
@ -35,43 +27,15 @@ import org.springframework.util.ClassUtils;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Moritz Halbritter * @author Moritz Halbritter
* @since 2.1.0 * @since 2.1.0
* @deprecated since 3.0.0 for removal in 3.2.0 in favor of
* {@link ClientHttpRequestFactories}
*/ */
@Deprecated(since = "3.0.0", forRemoval = true)
public class ClientHttpRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> { public class ClientHttpRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {
private static final String APACHE_HTTP_CLIENT_CLASS = "org.apache.hc.client5.http.impl.classic.HttpClients";
private static final boolean APACHE_HTTP_CLIENT_PRESENT = ClassUtils.isPresent(APACHE_HTTP_CLIENT_CLASS, null);
private static final String OKHTTP_CLIENT_CLASS = "okhttp3.OkHttpClient";
private static final boolean OKHTTP_CLIENT_PRESENT = ClassUtils.isPresent(OKHTTP_CLIENT_CLASS, null);
@Override @Override
public ClientHttpRequestFactory get() { public ClientHttpRequestFactory get() {
if (APACHE_HTTP_CLIENT_PRESENT) { return ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS);
return new HttpComponentsClientHttpRequestFactory();
}
if (OKHTTP_CLIENT_PRESENT) {
return new OkHttp3ClientHttpRequestFactory();
}
return new SimpleClientHttpRequestFactory();
}
static class ClientHttpRequestFactorySupplierRuntimeHints {
static void registerHints(RuntimeHints hints, ClassLoader classLoader, Consumer<Builder> callback) {
if (ClassUtils.isPresent(APACHE_HTTP_CLIENT_CLASS, classLoader)) {
hints.reflection().registerType(HttpComponentsClientHttpRequestFactory.class, (typeHint) -> callback
.accept(typeHint.onReachableType(TypeReference.of(APACHE_HTTP_CLIENT_CLASS))));
}
if (ClassUtils.isPresent(OKHTTP_CLIENT_CLASS, classLoader)) {
hints.reflection().registerType(OkHttp3ClientHttpRequestFactory.class,
(typeHint) -> callback.accept(typeHint.onReachableType(TypeReference.of(OKHTTP_CLIENT_CLASS))));
}
hints.reflection().registerType(SimpleClientHttpRequestFactory.class, (typeHint) -> callback
.accept(typeHint.onReachableType(TypeReference.of(SimpleClientHttpRequestFactory.class))));
}
} }
} }

@ -16,9 +16,6 @@
package org.springframework.boot.web.client; package org.springframework.boot.web.client;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,20 +26,13 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import reactor.netty.http.client.HttpClientRequest; import reactor.netty.http.client.HttpClientRequest;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
@ -51,7 +41,6 @@ import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler; import org.springframework.web.util.UriTemplateHandler;
@ -77,10 +66,9 @@ import org.springframework.web.util.UriTemplateHandler;
* @author Ilya Lukyanovich * @author Ilya Lukyanovich
* @since 1.4.0 * @since 1.4.0
*/ */
@ImportRuntimeHints(RestTemplateBuilder.RestTemplateBuilderRuntimeHints.class)
public class RestTemplateBuilder { public class RestTemplateBuilder {
private final RequestFactoryCustomizer requestFactoryCustomizer; private final ClientHttpRequestFactorySettings requestFactorySettings;
private final boolean detectRequestFactory; private final boolean detectRequestFactory;
@ -90,7 +78,7 @@ public class RestTemplateBuilder {
private final Set<ClientHttpRequestInterceptor> interceptors; private final Set<ClientHttpRequestInterceptor> interceptors;
private final Supplier<ClientHttpRequestFactory> requestFactory; private final Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory> requestFactory;
private final UriTemplateHandler uriTemplateHandler; private final UriTemplateHandler uriTemplateHandler;
@ -111,7 +99,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder(RestTemplateCustomizer... customizers) { public RestTemplateBuilder(RestTemplateCustomizer... customizers) {
Assert.notNull(customizers, "Customizers must not be null"); Assert.notNull(customizers, "Customizers must not be null");
this.requestFactoryCustomizer = new RequestFactoryCustomizer(); this.requestFactorySettings = ClientHttpRequestFactorySettings.DEFAULTS;
this.detectRequestFactory = true; this.detectRequestFactory = true;
this.rootUri = null; this.rootUri = null;
this.messageConverters = null; this.messageConverters = null;
@ -125,18 +113,19 @@ public class RestTemplateBuilder {
this.requestCustomizers = Collections.emptySet(); this.requestCustomizers = Collections.emptySet();
} }
private RestTemplateBuilder(RequestFactoryCustomizer requestFactoryCustomizer, boolean detectRequestFactory, private RestTemplateBuilder(ClientHttpRequestFactorySettings requestFactorySettings, boolean detectRequestFactory,
String rootUri, Set<HttpMessageConverter<?>> messageConverters, String rootUri, Set<HttpMessageConverter<?>> messageConverters,
Set<ClientHttpRequestInterceptor> interceptors, Supplier<ClientHttpRequestFactory> requestFactorySupplier, Set<ClientHttpRequestInterceptor> interceptors,
Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory> requestFactory,
UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler, UriTemplateHandler uriTemplateHandler, ResponseErrorHandler errorHandler,
BasicAuthentication basicAuthentication, Map<String, List<String>> defaultHeaders, BasicAuthentication basicAuthentication, Map<String, List<String>> defaultHeaders,
Set<RestTemplateCustomizer> customizers, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) { Set<RestTemplateCustomizer> customizers, Set<RestTemplateRequestCustomizer<?>> requestCustomizers) {
this.requestFactoryCustomizer = requestFactoryCustomizer; this.requestFactorySettings = requestFactorySettings;
this.detectRequestFactory = detectRequestFactory; this.detectRequestFactory = detectRequestFactory;
this.rootUri = rootUri; this.rootUri = rootUri;
this.messageConverters = messageConverters; this.messageConverters = messageConverters;
this.interceptors = interceptors; this.interceptors = interceptors;
this.requestFactory = requestFactorySupplier; this.requestFactory = requestFactory;
this.uriTemplateHandler = uriTemplateHandler; this.uriTemplateHandler = uriTemplateHandler;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.basicAuthentication = basicAuthentication; this.basicAuthentication = basicAuthentication;
@ -153,7 +142,7 @@ public class RestTemplateBuilder {
* @return a new builder instance * @return a new builder instance
*/ */
public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) { public RestTemplateBuilder detectRequestFactory(boolean detectRequestFactory) {
return new RestTemplateBuilder(this.requestFactoryCustomizer, detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers); this.requestCustomizers);
@ -169,7 +158,7 @@ public class RestTemplateBuilder {
* @return a new builder instance * @return a new builder instance
*/ */
public RestTemplateBuilder rootUri(String rootUri) { public RestTemplateBuilder rootUri(String rootUri) {
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers); this.requestCustomizers);
@ -200,7 +189,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder messageConverters(Collection<? extends HttpMessageConverter<?>> messageConverters) { public RestTemplateBuilder messageConverters(Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null"); Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
copiedSetOf(messageConverters), this.interceptors, this.requestFactory, this.uriTemplateHandler, copiedSetOf(messageConverters), this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers); this.requestCustomizers);
@ -230,7 +219,7 @@ public class RestTemplateBuilder {
public RestTemplateBuilder additionalMessageConverters( public RestTemplateBuilder additionalMessageConverters(
Collection<? extends HttpMessageConverter<?>> messageConverters) { Collection<? extends HttpMessageConverter<?>> messageConverters) {
Assert.notNull(messageConverters, "MessageConverters must not be null"); Assert.notNull(messageConverters, "MessageConverters must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
append(this.messageConverters, messageConverters), this.interceptors, this.requestFactory, append(this.messageConverters, messageConverters), this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -244,7 +233,7 @@ public class RestTemplateBuilder {
* @see #messageConverters(HttpMessageConverter...) * @see #messageConverters(HttpMessageConverter...)
*/ */
public RestTemplateBuilder defaultMessageConverters() { public RestTemplateBuilder defaultMessageConverters() {
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
copiedSetOf(new RestTemplate().getMessageConverters()), this.interceptors, this.requestFactory, copiedSetOf(new RestTemplate().getMessageConverters()), this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -275,7 +264,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder interceptors(Collection<ClientHttpRequestInterceptor> interceptors) { public RestTemplateBuilder interceptors(Collection<ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "interceptors must not be null"); Assert.notNull(interceptors, "interceptors must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, copiedSetOf(interceptors), this.requestFactory, this.uriTemplateHandler, this.messageConverters, copiedSetOf(interceptors), this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers); this.requestCustomizers);
@ -304,7 +293,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalInterceptors(Collection<? extends ClientHttpRequestInterceptor> interceptors) { public RestTemplateBuilder additionalInterceptors(Collection<? extends ClientHttpRequestInterceptor> interceptors) {
Assert.notNull(interceptors, "interceptors must not be null"); Assert.notNull(interceptors, "interceptors must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, append(this.interceptors, interceptors), this.requestFactory, this.messageConverters, append(this.interceptors, interceptors), this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -313,37 +302,41 @@ public class RestTemplateBuilder {
/** /**
* Set the {@link ClientHttpRequestFactory} class that should be used with the * Set the {@link ClientHttpRequestFactory} class that should be used with the
* {@link RestTemplate}. * {@link RestTemplate}.
* @param requestFactory the request factory to use * @param requestFactoryType the request factory type to use
* @return a new builder instance * @return a new builder instance
*/ */
public RestTemplateBuilder requestFactory(Class<? extends ClientHttpRequestFactory> requestFactory) { public RestTemplateBuilder requestFactory(Class<? extends ClientHttpRequestFactory> requestFactoryType) {
Assert.notNull(requestFactory, "RequestFactory must not be null"); Assert.notNull(requestFactoryType, "RequestFactoryType must not be null");
return requestFactory(() -> createRequestFactory(requestFactory)); return requestFactory((settings) -> ClientHttpRequestFactories.get(requestFactoryType, settings));
}
private ClientHttpRequestFactory createRequestFactory(Class<? extends ClientHttpRequestFactory> requestFactory) {
try {
Constructor<?> constructor = requestFactory.getDeclaredConstructor();
constructor.setAccessible(true);
return (ClientHttpRequestFactory) constructor.newInstance();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
} }
/** /**
* Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called * Set the {@code Supplier} of {@link ClientHttpRequestFactory} that should be called
* each time we {@link #build()} a new {@link RestTemplate} instance. * each time we {@link #build()} a new {@link RestTemplate} instance.
* @param requestFactory the supplier for the request factory * @param requestFactorySupplier the supplier for the request factory
* @return a new builder instance * @return a new builder instance
* @since 2.0.0 * @since 2.0.0
*/ */
public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactory) { public RestTemplateBuilder requestFactory(Supplier<ClientHttpRequestFactory> requestFactorySupplier) {
Assert.notNull(requestFactory, "RequestFactory Supplier must not be null"); Assert.notNull(requestFactorySupplier, "RequestFactorySupplier must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return requestFactory((settings) -> ClientHttpRequestFactories.get(requestFactorySupplier, settings));
this.messageConverters, this.interceptors, requestFactory, this.uriTemplateHandler, this.errorHandler, }
this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
/**
* Set the {@link ClientHttpRequestFactorySupplier} that should be called each time we
* {@link #build()} a new {@link RestTemplate} instance.
* @param requestFactoryFunction the settings to request factory function
* @return a new builder instance
* @since 3.0.0
* @see ClientHttpRequestFactories
*/
public RestTemplateBuilder requestFactory(
Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory> requestFactoryFunction) {
Assert.notNull(requestFactoryFunction, "RequestFactoryFunction must not be null");
return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, requestFactoryFunction, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
this.requestCustomizers);
} }
/** /**
@ -354,7 +347,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) { public RestTemplateBuilder uriTemplateHandler(UriTemplateHandler uriTemplateHandler) {
Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null"); Assert.notNull(uriTemplateHandler, "UriTemplateHandler must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, uriTemplateHandler, this.errorHandler, this.messageConverters, this.interceptors, this.requestFactory, uriTemplateHandler, this.errorHandler,
this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers); this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
} }
@ -367,7 +360,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) { public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ErrorHandler must not be null"); Assert.notNull(errorHandler, "ErrorHandler must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, errorHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, errorHandler,
this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers); this.basicAuthentication, this.defaultHeaders, this.customizers, this.requestCustomizers);
} }
@ -395,7 +388,7 @@ public class RestTemplateBuilder {
* @since 2.2.0 * @since 2.2.0
*/ */
public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) { public RestTemplateBuilder basicAuthentication(String username, String password, Charset charset) {
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, new BasicAuthentication(username, password, charset), this.defaultHeaders, this.errorHandler, new BasicAuthentication(username, password, charset), this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -412,7 +405,7 @@ public class RestTemplateBuilder {
public RestTemplateBuilder defaultHeader(String name, String... values) { public RestTemplateBuilder defaultHeader(String name, String... values) {
Assert.notNull(name, "Name must not be null"); Assert.notNull(name, "Name must not be null");
Assert.notNull(values, "Values must not be null"); Assert.notNull(values, "Values must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, append(this.defaultHeaders, name, values), this.errorHandler, this.basicAuthentication, append(this.defaultHeaders, name, values),
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -425,7 +418,7 @@ public class RestTemplateBuilder {
* @since 2.1.0 * @since 2.1.0
*/ */
public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) { public RestTemplateBuilder setConnectTimeout(Duration connectTimeout) {
return new RestTemplateBuilder(this.requestFactoryCustomizer.connectTimeout(connectTimeout), return new RestTemplateBuilder(this.requestFactorySettings.withConnectTimeout(connectTimeout),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory, this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -438,7 +431,7 @@ public class RestTemplateBuilder {
* @since 2.1.0 * @since 2.1.0
*/ */
public RestTemplateBuilder setReadTimeout(Duration readTimeout) { public RestTemplateBuilder setReadTimeout(Duration readTimeout) {
return new RestTemplateBuilder(this.requestFactoryCustomizer.readTimeout(readTimeout), return new RestTemplateBuilder(this.requestFactorySettings.withReadTimeout(readTimeout),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory, this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -454,7 +447,7 @@ public class RestTemplateBuilder {
* @see HttpComponentsClientHttpRequestFactory#setBufferRequestBody(boolean) * @see HttpComponentsClientHttpRequestFactory#setBufferRequestBody(boolean)
*/ */
public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) { public RestTemplateBuilder setBufferRequestBody(boolean bufferRequestBody) {
return new RestTemplateBuilder(this.requestFactoryCustomizer.bufferRequestBody(bufferRequestBody), return new RestTemplateBuilder(this.requestFactorySettings.withBufferRequestBody(bufferRequestBody),
this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory, this.detectRequestFactory, this.rootUri, this.messageConverters, this.interceptors, this.requestFactory,
this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.uriTemplateHandler, this.errorHandler, this.basicAuthentication, this.defaultHeaders,
this.customizers, this.requestCustomizers); this.customizers, this.requestCustomizers);
@ -485,7 +478,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> customizers) { public RestTemplateBuilder customizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "Customizers must not be null"); Assert.notNull(customizers, "Customizers must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, copiedSetOf(customizers), this.errorHandler, this.basicAuthentication, this.defaultHeaders, copiedSetOf(customizers),
this.requestCustomizers); this.requestCustomizers);
@ -514,7 +507,7 @@ public class RestTemplateBuilder {
*/ */
public RestTemplateBuilder additionalCustomizers(Collection<? extends RestTemplateCustomizer> customizers) { public RestTemplateBuilder additionalCustomizers(Collection<? extends RestTemplateCustomizer> customizers) {
Assert.notNull(customizers, "RestTemplateCustomizers must not be null"); Assert.notNull(customizers, "RestTemplateCustomizers must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, append(this.customizers, customizers), this.errorHandler, this.basicAuthentication, this.defaultHeaders, append(this.customizers, customizers),
this.requestCustomizers); this.requestCustomizers);
@ -548,7 +541,7 @@ public class RestTemplateBuilder {
public RestTemplateBuilder requestCustomizers( public RestTemplateBuilder requestCustomizers(
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) { Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
Assert.notNull(requestCustomizers, "RequestCustomizers must not be null"); Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
copiedSetOf(requestCustomizers)); copiedSetOf(requestCustomizers));
@ -580,7 +573,7 @@ public class RestTemplateBuilder {
public RestTemplateBuilder additionalRequestCustomizers( public RestTemplateBuilder additionalRequestCustomizers(
Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) { Collection<? extends RestTemplateRequestCustomizer<?>> requestCustomizers) {
Assert.notNull(requestCustomizers, "RequestCustomizers must not be null"); Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
return new RestTemplateBuilder(this.requestFactoryCustomizer, this.detectRequestFactory, this.rootUri, return new RestTemplateBuilder(this.requestFactorySettings, this.detectRequestFactory, this.rootUri,
this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler, this.messageConverters, this.interceptors, this.requestFactory, this.uriTemplateHandler,
this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers, this.errorHandler, this.basicAuthentication, this.defaultHeaders, this.customizers,
append(this.requestCustomizers, requestCustomizers)); append(this.requestCustomizers, requestCustomizers));
@ -651,19 +644,13 @@ public class RestTemplateBuilder {
* @since 2.2.0 * @since 2.2.0
*/ */
public ClientHttpRequestFactory buildRequestFactory() { public ClientHttpRequestFactory buildRequestFactory() {
ClientHttpRequestFactory requestFactory = null;
if (this.requestFactory != null) { if (this.requestFactory != null) {
requestFactory = this.requestFactory.get(); return this.requestFactory.apply(this.requestFactorySettings);
} }
else if (this.detectRequestFactory) { if (this.detectRequestFactory) {
requestFactory = new ClientHttpRequestFactorySupplier().get(); return ClientHttpRequestFactories.get(this.requestFactorySettings);
} }
if (requestFactory != null) { return null;
if (this.requestFactoryCustomizer != null) {
this.requestFactoryCustomizer.accept(requestFactory);
}
}
return requestFactory;
} }
private void addClientHttpRequestInitializer(RestTemplate restTemplate) { private void addClientHttpRequestInitializer(RestTemplate restTemplate) {
@ -703,118 +690,4 @@ public class RestTemplateBuilder {
return Collections.unmodifiableMap(result); return Collections.unmodifiableMap(result);
} }
/**
* Internal customizer used to apply {@link ClientHttpRequestFactory} settings.
*/
private static class RequestFactoryCustomizer implements Consumer<ClientHttpRequestFactory> {
private final Duration connectTimeout;
private final Duration readTimeout;
private final Boolean bufferRequestBody;
RequestFactoryCustomizer() {
this(null, null, null);
}
private RequestFactoryCustomizer(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody) {
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.bufferRequestBody = bufferRequestBody;
}
RequestFactoryCustomizer connectTimeout(Duration connectTimeout) {
return new RequestFactoryCustomizer(connectTimeout, this.readTimeout, this.bufferRequestBody);
}
RequestFactoryCustomizer readTimeout(Duration readTimeout) {
return new RequestFactoryCustomizer(this.connectTimeout, readTimeout, this.bufferRequestBody);
}
RequestFactoryCustomizer bufferRequestBody(boolean bufferRequestBody) {
return new RequestFactoryCustomizer(this.connectTimeout, this.readTimeout, bufferRequestBody);
}
@Override
public void accept(ClientHttpRequestFactory requestFactory) {
ClientHttpRequestFactory unwrappedRequestFactory = unwrapRequestFactoryIfNecessary(requestFactory);
if (this.connectTimeout != null) {
setConnectTimeout(unwrappedRequestFactory);
}
if (this.readTimeout != null) {
setReadTimeout(unwrappedRequestFactory);
}
if (this.bufferRequestBody != null) {
setBufferRequestBody(unwrappedRequestFactory);
}
}
private ClientHttpRequestFactory unwrapRequestFactoryIfNecessary(ClientHttpRequestFactory requestFactory) {
if (!(requestFactory instanceof AbstractClientHttpRequestFactoryWrapper)) {
return requestFactory;
}
Field field = ReflectionUtils.findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory");
ReflectionUtils.makeAccessible(field);
ClientHttpRequestFactory unwrappedRequestFactory = requestFactory;
while (unwrappedRequestFactory instanceof AbstractClientHttpRequestFactoryWrapper) {
unwrappedRequestFactory = (ClientHttpRequestFactory) ReflectionUtils.getField(field,
unwrappedRequestFactory);
}
return unwrappedRequestFactory;
}
private void setConnectTimeout(ClientHttpRequestFactory factory) {
Method method = findMethod(factory, "setConnectTimeout", int.class);
int timeout = Math.toIntExact(this.connectTimeout.toMillis());
invoke(factory, method, timeout);
}
private void setReadTimeout(ClientHttpRequestFactory factory) {
Method method = findMethod(factory, "setReadTimeout", int.class);
int timeout = Math.toIntExact(this.readTimeout.toMillis());
invoke(factory, method, timeout);
}
private void setBufferRequestBody(ClientHttpRequestFactory factory) {
Method method = findMethod(factory, "setBufferRequestBody", boolean.class);
invoke(factory, method, this.bufferRequestBody);
}
private Method findMethod(ClientHttpRequestFactory requestFactory, String methodName, Class<?>... parameters) {
Method method = ReflectionUtils.findMethod(requestFactory.getClass(), methodName, parameters);
if (method == null) {
throw new IllegalStateException("Request factory " + requestFactory.getClass()
+ " does not have a suitable " + methodName + " method");
}
else if (method.isAnnotationPresent(Deprecated.class)) {
throw new IllegalStateException("Request factory " + requestFactory.getClass() + " has the "
+ methodName + " method marked as deprecated");
}
return method;
}
private void invoke(ClientHttpRequestFactory requestFactory, Method method, Object... parameters) {
ReflectionUtils.invokeMethod(method, requestFactory, parameters);
}
}
static class RestTemplateBuilderRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerField(Objects.requireNonNull(
ReflectionUtils.findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory")));
ClientHttpRequestFactorySupplier.ClientHttpRequestFactorySupplierRuntimeHints.registerHints(hints,
classLoader, (hint) -> {
hint.withMethod("setConnectTimeout", TypeReference.listOf(int.class), ExecutableMode.INVOKE);
hint.withMethod("setReadTimeout", TypeReference.listOf(int.class), ExecutableMode.INVOKE);
hint.withMethod("setBufferRequestBody", TypeReference.listOf(boolean.class),
ExecutableMode.INVOKE);
});
}
}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,14 +16,14 @@
package org.springframework.boot.webservices.client; package org.springframework.boot.webservices.client;
import java.lang.reflect.Method;
import java.time.Duration; import java.time.Duration;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.web.client.ClientHttpRequestFactorySupplier; import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.ws.transport.WebServiceMessageSender; import org.springframework.ws.transport.WebServiceMessageSender;
import org.springframework.ws.transport.http.ClientHttpRequestMessageSender; import org.springframework.ws.transport.http.ClientHttpRequestMessageSender;
@ -40,7 +40,7 @@ public class HttpWebServiceMessageSenderBuilder {
private Duration readTimeout; private Duration readTimeout;
private Supplier<ClientHttpRequestFactory> requestFactorySupplier; private Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory> requestFactory;
/** /**
* Set the connection timeout. * Set the connection timeout.
@ -70,50 +70,39 @@ public class HttpWebServiceMessageSenderBuilder {
*/ */
public HttpWebServiceMessageSenderBuilder requestFactory( public HttpWebServiceMessageSenderBuilder requestFactory(
Supplier<ClientHttpRequestFactory> requestFactorySupplier) { Supplier<ClientHttpRequestFactory> requestFactorySupplier) {
Assert.notNull(requestFactorySupplier, "RequestFactory Supplier must not be null"); Assert.notNull(requestFactorySupplier, "RequestFactorySupplier must not be null");
this.requestFactorySupplier = requestFactorySupplier; this.requestFactory = (settings) -> ClientHttpRequestFactories.get(requestFactorySupplier, settings);
return this; return this;
} }
public WebServiceMessageSender build() { /**
ClientHttpRequestFactory requestFactory = (this.requestFactorySupplier != null) * Set the {@code Function} of {@link ClientHttpRequestFactorySettings} to
? this.requestFactorySupplier.get() : new ClientHttpRequestFactorySupplier().get(); * {@link ClientHttpRequestFactory} that should be called to create the HTTP-based
if (this.connectTimeout != null) { * {@link WebServiceMessageSender}.
new TimeoutRequestFactoryCustomizer(this.connectTimeout, "setConnectTimeout").customize(requestFactory); * @param requestFactoryFunction the function for the request factory
} * @return a new builder instance
if (this.readTimeout != null) { * @since 3.0.0
new TimeoutRequestFactoryCustomizer(this.readTimeout, "setReadTimeout").customize(requestFactory); */
} public HttpWebServiceMessageSenderBuilder requestFactory(
return new ClientHttpRequestMessageSender(requestFactory); Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory> requestFactoryFunction) {
Assert.notNull(requestFactoryFunction, "RequestFactoryFunction must not be null");
this.requestFactory = requestFactoryFunction;
return this;
} }
/** /**
* {@link ClientHttpRequestFactory} customizer to call a "set timeout" method. * Build the {@link WebServiceMessageSender} instance.
* @return the {@link WebServiceMessageSender} instance
*/ */
private static class TimeoutRequestFactoryCustomizer { public WebServiceMessageSender build() {
return new ClientHttpRequestMessageSender(getRequestFactory());
private final Duration timeout; }
private final String methodName;
TimeoutRequestFactoryCustomizer(Duration timeout, String methodName) {
this.timeout = timeout;
this.methodName = methodName;
}
void customize(ClientHttpRequestFactory factory) {
ReflectionUtils.invokeMethod(findMethod(factory), factory, Math.toIntExact(this.timeout.toMillis()));
}
private Method findMethod(ClientHttpRequestFactory factory) {
Method method = ReflectionUtils.findMethod(factory.getClass(), this.methodName, int.class);
if (method != null) {
return method;
}
throw new IllegalStateException(
"Request factory " + factory.getClass() + " does not have a " + this.methodName + "(int) method");
}
private ClientHttpRequestFactory getRequestFactory() {
ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(this.connectTimeout,
this.readTimeout, null);
return (this.requestFactory != null) ? this.requestFactory.apply(settings)
: ClientHttpRequestFactories.get(settings);
} }
} }

@ -7,6 +7,7 @@ org.springframework.boot.env.PropertySourceRuntimeHints,\
org.springframework.boot.json.JacksonRuntimeHints,\ org.springframework.boot.json.JacksonRuntimeHints,\
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\ org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
org.springframework.boot.logging.logback.LogbackRuntimeHints,\ org.springframework.boot.logging.logback.LogbackRuntimeHints,\
org.springframework.boot.web.client.ClientHttpRequestFactoriesRuntimeHints,\
org.springframework.boot.web.embedded.undertow.UndertowWebServer.UndertowWebServerRuntimeHints,\ org.springframework.boot.web.embedded.undertow.UndertowWebServer.UndertowWebServerRuntimeHints,\
org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints

@ -40,6 +40,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
@ -50,7 +51,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}. * Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor} and
* {@link BindableRuntimeHintsRegistrar}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Moritz Halbritter * @author Moritz Halbritter

@ -0,0 +1,83 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.ClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base classes for testing of {@link ClientHttpRequestFactories} with different HTTP
* clients on the classpath.
*
* @param <T> the {@link ClientHttpRequestFactory} to be produced
* @author Andy Wilkinson
*/
abstract class AbstractClientHttpRequestFactoriesTests<T extends ClientHttpRequestFactory> {
private final Class<T> requestFactoryType;
protected AbstractClientHttpRequestFactoriesTests(Class<T> requestFactoryType) {
this.requestFactoryType = requestFactoryType;
}
@Test
void getReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
void getOfGeneralTypeReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(ClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
void getOfSpecificTypeReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(this.requestFactoryType,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(this.requestFactoryType);
}
@Test
@SuppressWarnings("unchecked")
void getReturnsRequestFactoryWithConfiguredConnectTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofSeconds(60)));
assertThat(connectTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
@SuppressWarnings("unchecked")
void getReturnsRequestFactoryWithConfiguredReadTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofSeconds(120)));
assertThat(readTimeout((T) requestFactory)).isEqualTo(Duration.ofSeconds(120).toMillis());
}
protected abstract long connectTimeout(T requestFactory);
protected abstract long readTimeout(T requestFactory);
}

@ -0,0 +1,80 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.ClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for tests that verify the configuration of the
* {@link ClientHttpRequestFactory} used by {@link RestTemplateBuilder}.
*
* @param <T> the request factory type under test
* @author Andy Wilkinson
*/
abstract class AbstractRestTemplateBuilderRequestFactoryConfigurationTests<T extends ClientHttpRequestFactory> {
private final Class<? extends ClientHttpRequestFactory> factoryType;
private final RestTemplateBuilder builder = new RestTemplateBuilder();
protected AbstractRestTemplateBuilderRequestFactoryConfigurationTests(Class<T> factoryType) {
this.factoryType = factoryType;
}
@Test
@SuppressWarnings("unchecked")
void connectTimeoutCanBeConfiguredOnFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(this.factoryType)
.setConnectTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(connectTimeout((T) requestFactory)).isEqualTo(1234);
}
@Test
@SuppressWarnings("unchecked")
void readTimeoutCanBeConfiguredOnFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(this.factoryType)
.setReadTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(readTimeout((T) requestFactory)).isEqualTo(1234);
}
@Test
@SuppressWarnings("unchecked")
void connectTimeoutCanBeConfiguredOnDetectedFactory() {
ClientHttpRequestFactory requestFactory = this.builder.setConnectTimeout(Duration.ofMillis(1234)).build()
.getRequestFactory();
assertThat(connectTimeout((T) requestFactory)).isEqualTo(1234);
}
@Test
@SuppressWarnings("unchecked")
void readTimeoutCanBeConfiguredOnDetectedFactory() {
ClientHttpRequestFactory requestFactory = this.builder.setReadTimeout(Duration.ofMillis(1234)).build()
.getRequestFactory();
assertThat(readTimeout((T) requestFactory)).isEqualTo(1234);
}
protected abstract long connectTimeout(T requestFactory);
protected abstract long readTimeout(T requestFactory);
}

@ -0,0 +1,52 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.core5.http.io.SocketConfig;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when Apache Http Components is the
* predominant HTTP client.
*
* @author Andy Wilkinson
*/
class ClientHttpRequestFactoriesHttpComponentsTests
extends AbstractClientHttpRequestFactoriesTests<HttpComponentsClientHttpRequestFactory> {
ClientHttpRequestFactoriesHttpComponentsTests() {
super(HttpComponentsClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
protected long readTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
HttpClient httpClient = requestFactory.getHttpClient();
Object connectionManager = ReflectionTestUtils.getField(httpClient, "connManager");
SocketConfig socketConfig = (SocketConfig) ReflectionTestUtils.getField(connectionManager,
"defaultSocketConfig");
return socketConfig.getSoTimeout().toMilliseconds();
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.io.File;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ClientHttpRequestFactories} when OkHttp 3 is the predominant HTTP
* client.
*
* @author Andy Wilkinson
*/
@ClassPathOverrides("com.squareup.okhttp3:okhttp:3.14.9")
@ClassPathExclusions("httpclient5-*.jar")
class ClientHttpRequestFactoriesOkHttp3Tests
extends AbstractClientHttpRequestFactoriesTests<OkHttp3ClientHttpRequestFactory> {
ClientHttpRequestFactoriesOkHttp3Tests() {
super(OkHttp3ClientHttpRequestFactory.class);
}
@Test
void okHttp3IsBeingUsed() {
assertThat(new File(OkHttpClient.class.getProtectionDomain().getCodeSource().getLocation().getFile()).getName())
.startsWith("okhttp-3.");
}
@Test
void getFailsWhenBufferRequestBodyIsEnabled() {
assertThatIllegalStateException().isThrownBy(() -> ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true)));
}
@Override
protected long connectTimeout(OkHttp3ClientHttpRequestFactory requestFactory) {
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).connectTimeoutMillis();
}
@Override
protected long readTimeout(OkHttp3ClientHttpRequestFactory requestFactory) {
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis();
}
}

@ -0,0 +1,67 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.io.File;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ClientHttpRequestFactories} when OkHttp 4 is the predominant HTTP
* client.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions("httpclient5-*.jar")
class ClientHttpRequestFactoriesOkHttp4Tests
extends AbstractClientHttpRequestFactoriesTests<OkHttp3ClientHttpRequestFactory> {
ClientHttpRequestFactoriesOkHttp4Tests() {
super(OkHttp3ClientHttpRequestFactory.class);
}
@Test
void okHttp4IsBeingUsed() {
assertThat(new File(OkHttpClient.class.getProtectionDomain().getCodeSource().getLocation().getFile()).getName())
.startsWith("okhttp-4.");
}
@Test
void getFailsWhenBufferRequestBodyIsEnabled() {
assertThatIllegalStateException().isThrownBy(() -> ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true)));
}
@Override
protected long connectTimeout(OkHttp3ClientHttpRequestFactory requestFactory) {
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).connectTimeoutMillis();
}
@Override
protected long readTimeout(OkHttp3ClientHttpRequestFactory requestFactory) {
return ((OkHttpClient) ReflectionTestUtils.getField(requestFactory, "client")).readTimeoutMillis();
}
}

@ -0,0 +1,91 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ClientHttpRequestFactoriesRuntimeHints}.
*
* @author Andy Wilkinson
*/
public class ClientHttpRequestFactoriesRuntimeHintsTests {
@Test
void shouldRegisterHints() {
RuntimeHints hints = new RuntimeHints();
new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection
.onField(ReflectionUtils.findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory")))
.accepts(hints);
}
@Test
void shouldRegisterHttpComponentHints() {
RuntimeHints hints = new RuntimeHints();
new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class,
"setConnectTimeout", int.class))).accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class,
"setBufferRequestBody", boolean.class))).accepts(hints);
}
@Test
void shouldRegisterOkHttpHints() {
RuntimeHints hints = new RuntimeHints();
new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(OkHttp3ClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(OkHttp3ClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
}
@Test
void shouldRegisterSimpleHttpHints() {
RuntimeHints hints = new RuntimeHints();
new ClientHttpRequestFactoriesRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class,
"setBufferRequestBody", boolean.class))).accepts(hints);
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link ClientHttpRequestFactories} when the simple JDK-based client is the
* predominant HTTP client.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions({ "httpclient5-*.jar", "okhttp-*.jar" })
class ClientHttpRequestFactoriesSimpleTests
extends AbstractClientHttpRequestFactoriesTests<SimpleClientHttpRequestFactory> {
ClientHttpRequestFactoriesSimpleTests() {
super(SimpleClientHttpRequestFactory.class);
}
@Override
protected long connectTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "connectTimeout");
}
@Override
protected long readTimeout(SimpleClientHttpRequestFactory requestFactory) {
return (int) ReflectionTestUtils.getField(requestFactory, "readTimeout");
}
}

@ -0,0 +1,247 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ClientHttpRequestFactories}.
*
* @author Andy Wilkinson
*/
class ClientHttpRequestFactoriesTests {
@Test
void getReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
@Test
void getOfGeneralTypeReturnsRequestFactoryOfExpectedType() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(ClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
@Test
void getOfSimpleFactoryReturnsSimpleFactory() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(SimpleClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(SimpleClientHttpRequestFactory.class);
}
@Test
void getOfHttpComponentsFactoryReturnsHttpComponentsFactory() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories
.get(HttpComponentsClientHttpRequestFactory.class, ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
}
@Test
void getOfOkHttpFactoryReturnsOkHttpFactory() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(OkHttp3ClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class);
}
@Test
void getOfUnknownTypeCreatesFactory() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS);
assertThat(requestFactory).isInstanceOf(TestClientHttpRequestFactory.class);
}
@Test
void getOfUnknownTypeWithConnectTimeoutCreatesFactoryAndConfiguresConnectTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofSeconds(60)));
assertThat(requestFactory).isInstanceOf(TestClientHttpRequestFactory.class);
assertThat(((TestClientHttpRequestFactory) requestFactory).connectTimeout)
.isEqualTo(Duration.ofSeconds(60).toMillis());
}
@Test
void getOfUnknownTypeWithReadTimeoutCreatesFactoryAndConfiguresReadTimeout() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofSeconds(90)));
assertThat(requestFactory).isInstanceOf(TestClientHttpRequestFactory.class);
assertThat(((TestClientHttpRequestFactory) requestFactory).readTimeout)
.isEqualTo(Duration.ofSeconds(90).toMillis());
}
@Test
void getOfUnknownTypeWithBodyBufferingCreatesFactoryAndConfiguresBodyBuffering() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(TestClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true));
assertThat(requestFactory).isInstanceOf(TestClientHttpRequestFactory.class);
assertThat(((TestClientHttpRequestFactory) requestFactory).bufferRequestBody).isTrue();
}
@Test
void getOfUnconfigurableTypeWithConnectTimeoutThrows() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(UnconfigurableClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofSeconds(60))))
.withMessageContaining("suitable setConnectTimeout method");
}
@Test
void getOfUnconfigurableTypeWithReadTimeoutThrows() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(UnconfigurableClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofSeconds(60))))
.withMessageContaining("suitable setReadTimeout method");
}
@Test
void getOfUnconfigurableTypeWithBodyBufferingThrows() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(UnconfigurableClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(true)))
.withMessageContaining("suitable setBufferRequestBody method");
}
@Test
void getOfTypeWithDeprecatedConnectTimeoutThrowsWithConnectTimeout() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(DeprecatedMethodsClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofSeconds(60))))
.withMessageContaining("setConnectTimeout method marked as deprecated");
}
@Test
void getOfTypeWithDeprecatedReadTimeoutThrowsWithReadTimeout() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(DeprecatedMethodsClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofSeconds(60))))
.withMessageContaining("setReadTimeout method marked as deprecated");
}
@Test
void getOfTypeWithDeprecatedBufferRequestBodyThrowsWithBufferRequestBody() {
assertThatIllegalStateException()
.isThrownBy(() -> ClientHttpRequestFactories.get(DeprecatedMethodsClientHttpRequestFactory.class,
ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(false)))
.withMessageContaining("setBufferRequestBody method marked as deprecated");
}
@Test
void connectTimeoutCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
BufferingClientHttpRequestFactory result = ClientHttpRequestFactories.get(
() -> new BufferingClientHttpRequestFactory(requestFactory),
ClientHttpRequestFactorySettings.DEFAULTS.withConnectTimeout(Duration.ofMillis(1234)));
assertThat(result).extracting("requestFactory").isSameAs(requestFactory);
assertThat(requestFactory).hasFieldOrPropertyWithValue("connectTimeout", 1234);
}
@Test
void readTimeoutCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
BufferingClientHttpRequestFactory result = ClientHttpRequestFactories.get(
() -> new BufferingClientHttpRequestFactory(requestFactory),
ClientHttpRequestFactorySettings.DEFAULTS.withReadTimeout(Duration.ofMillis(1234)));
assertThat(result).extracting("requestFactory").isSameAs(requestFactory);
assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", 1234);
}
@Test
void bufferRequestBodyCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
BufferingClientHttpRequestFactory result = ClientHttpRequestFactories.get(
() -> new BufferingClientHttpRequestFactory(requestFactory),
ClientHttpRequestFactorySettings.DEFAULTS.withBufferRequestBody(false));
assertThat(result).extracting("requestFactory").isSameAs(requestFactory);
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", false);
}
public static class TestClientHttpRequestFactory implements ClientHttpRequestFactory {
private int connectTimeout;
private int readTimeout;
private boolean bufferRequestBody;
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
throw new UnsupportedOperationException();
}
public void setConnectTimeout(int timeout) {
this.connectTimeout = timeout;
}
public void setReadTimeout(int timeout) {
this.readTimeout = timeout;
}
public void setBufferRequestBody(boolean bufferRequestBody) {
this.bufferRequestBody = bufferRequestBody;
}
}
public static class UnconfigurableClientHttpRequestFactory implements ClientHttpRequestFactory {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
throw new UnsupportedOperationException();
}
}
public static class DeprecatedMethodsClientHttpRequestFactory implements ClientHttpRequestFactory {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
throw new UnsupportedOperationException();
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setConnectTimeout(int timeout) {
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setReadTimeout(int timeout) {
}
@Deprecated(since = "3.0.0", forRemoval = false)
public void setBufferRequestBody(boolean bufferRequestBody) {
}
}
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ClientHttpRequestFactorySettings}.
*
* @author Phillip Webb
*/
class ClientHttpRequestFactorySettingsTests {
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
@Test
void defaultsHasNullValues() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS;
assertThat(settings.connectTimeout()).isNull();
assertThat(settings.readTimeout()).isNull();
assertThat(settings.bufferRequestBody()).isNull();
}
@Test
void withConnectTimeoutReturnsInstanceWithUpdatedConnectionTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withConnectTimeout(ONE_SECOND);
assertThat(settings.connectTimeout()).isEqualTo(ONE_SECOND);
assertThat(settings.readTimeout()).isNull();
assertThat(settings.bufferRequestBody()).isNull();
}
@Test
void withReadTimeoutReturnsInstanceWithUpdatedReadTimeout() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withReadTimeout(ONE_SECOND);
assertThat(settings.connectTimeout()).isNull();
assertThat(settings.readTimeout()).isEqualTo(ONE_SECOND);
assertThat(settings.bufferRequestBody()).isNull();
}
@Test
void withBufferRequestBodyReturnsInstanceWithUpdatedBufferRequestBody() {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
.withBufferRequestBody(true);
assertThat(settings.connectTimeout()).isNull();
assertThat(settings.readTimeout()).isNull();
assertThat(settings.bufferRequestBody()).isTrue();
}
}

@ -18,28 +18,21 @@ package org.springframework.boot.web.client;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import okhttp3.OkHttpClient;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.web.client.RestTemplateBuilder.RestTemplateBuilderRuntimeHints;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestFactory;
@ -47,22 +40,18 @@ import org.springframework.http.client.ClientHttpRequestInitializer;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.InterceptingClientHttpRequestFactory; import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler; import org.springframework.web.util.UriTemplateHandler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
@ -255,7 +244,7 @@ class RestTemplateBuilderTests {
void requestFactoryClassWhenFactoryIsNullShouldThrowException() { void requestFactoryClassWhenFactoryIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> this.builder.requestFactory((Class<ClientHttpRequestFactory>) null)) .isThrownBy(() -> this.builder.requestFactory((Class<ClientHttpRequestFactory>) null))
.withMessageContaining("RequestFactory must not be null"); .withMessageContaining("RequestFactoryType must not be null");
} }
@Test @Test
@ -274,7 +263,15 @@ class RestTemplateBuilderTests {
void requestFactoryWhenSupplierIsNullShouldThrowException() { void requestFactoryWhenSupplierIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> this.builder.requestFactory((Supplier<ClientHttpRequestFactory>) null)) .isThrownBy(() -> this.builder.requestFactory((Supplier<ClientHttpRequestFactory>) null))
.withMessageContaining("RequestFactory Supplier must not be null"); .withMessageContaining("RequestFactorySupplier must not be null");
}
@Test
void requestFactoryWhenFunctionIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.builder
.requestFactory((Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory>) null))
.withMessageContaining("RequestFactoryFunction must not be null");
} }
@Test @Test
@ -459,128 +456,6 @@ class RestTemplateBuilderTests {
assertThat(template.getRequestFactory()).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); assertThat(template.getRequestFactory()).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
} }
@Test
void connectTimeoutCanBeNullToUseDefault() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class)
.setConnectTimeout(null).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("connectTimeout", -1);
}
@Test
void readTimeoutCanBeNullToUseDefault() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class)
.setReadTimeout(null).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", -1);
}
@Test
void connectTimeoutCanBeConfiguredOnHttpComponentsRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder
.requestFactory(HttpComponentsClientHttpRequestFactory.class).setConnectTimeout(Duration.ofMillis(1234))
.build().getRequestFactory();
assertThat(((int) ReflectionTestUtils.getField(requestFactory, "connectTimeout"))).isEqualTo(1234);
}
@Test
void readTimeoutConfigurationFailsOnHttpComponentsRequestFactory() {
assertThatThrownBy(() -> this.builder.requestFactory(HttpComponentsClientHttpRequestFactory.class)
.setReadTimeout(Duration.ofMillis(1234)).build()).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("setReadTimeout method marked as deprecated");
}
@Test
void bufferRequestBodyCanBeConfiguredOnHttpComponentsRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder
.requestFactory(HttpComponentsClientHttpRequestFactory.class).setBufferRequestBody(false).build()
.getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", false);
requestFactory = this.builder.requestFactory(HttpComponentsClientHttpRequestFactory.class)
.setBufferRequestBody(true).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
requestFactory = this.builder.requestFactory(HttpComponentsClientHttpRequestFactory.class).build()
.getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
}
@Test
void connectTimeoutCanBeConfiguredOnSimpleRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class)
.setConnectTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("connectTimeout", 1234);
}
@Test
void readTimeoutCanBeConfiguredOnSimpleRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class)
.setReadTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", 1234);
}
@Test
void bufferRequestBodyCanBeConfiguredOnSimpleRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class)
.setBufferRequestBody(false).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", false);
requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class).setBufferRequestBody(true)
.build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
requestFactory = this.builder.requestFactory(SimpleClientHttpRequestFactory.class).build().getRequestFactory();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
}
@Test
void connectTimeoutCanBeConfiguredOnOkHttpRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setConnectTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).extracting("client", InstanceOfAssertFactories.type(OkHttpClient.class))
.extracting(OkHttpClient::connectTimeoutMillis).isEqualTo(1234);
}
@Test
void readTimeoutCanBeConfiguredOnOkHttp3RequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setReadTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).extracting("client", InstanceOfAssertFactories.type(OkHttpClient.class))
.extracting(OkHttpClient::readTimeoutMillis).isEqualTo(1234);
}
@Test
void bufferRequestBodyCanNotBeConfiguredOnOkHttp3RequestFactory() {
assertThatIllegalStateException()
.isThrownBy(() -> this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setBufferRequestBody(false).build().getRequestFactory())
.withMessageContaining(OkHttp3ClientHttpRequestFactory.class.getName());
}
@Test
void connectTimeoutCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory))
.setConnectTimeout(Duration.ofMillis(1234)).build();
assertThat(requestFactory).hasFieldOrPropertyWithValue("connectTimeout", 1234);
}
@Test
void readTimeoutCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory))
.setReadTimeout(Duration.ofMillis(1234)).build();
assertThat(requestFactory).hasFieldOrPropertyWithValue("readTimeout", 1234);
}
@Test
void bufferRequestBodyCanBeConfiguredOnAWrappedRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory))
.setBufferRequestBody(false).build();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", false);
this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory))
.setBufferRequestBody(true).build();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
this.builder.requestFactory(() -> new BufferingClientHttpRequestFactory(requestFactory)).build();
assertThat(requestFactory).hasFieldOrPropertyWithValue("bufferRequestBody", true);
}
@Test @Test
void unwrappingDoesNotAffectRequestFactoryThatIsSetOnTheBuiltTemplate() { void unwrappingDoesNotAffectRequestFactoryThatIsSetOnTheBuiltTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
@ -589,58 +464,6 @@ class RestTemplateBuilderTests {
assertThat(template.getRequestFactory()).isInstanceOf(BufferingClientHttpRequestFactory.class); assertThat(template.getRequestFactory()).isInstanceOf(BufferingClientHttpRequestFactory.class);
} }
@Test
void shouldRegisterHints() {
RuntimeHints hints = new RuntimeHints();
new RestTemplateBuilderRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection
.onField(ReflectionUtils.findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory")))
.accepts(hints);
}
@Test
void shouldRegisterHttpComponentHints() {
RuntimeHints hints = new RuntimeHints();
new RestTemplateBuilderRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class,
"setConnectTimeout", int.class))).accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(ReflectionUtils.findMethod(HttpComponentsClientHttpRequestFactory.class,
"setBufferRequestBody", boolean.class))).accepts(hints);
}
@Test
void shouldRegisterOkHttpHints() {
RuntimeHints hints = new RuntimeHints();
new RestTemplateBuilderRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(OkHttp3ClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(OkHttp3ClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
}
@Test
void shouldRegisterSimpleHttpHints() {
RuntimeHints hints = new RuntimeHints();
new RestTemplateBuilderRuntimeHints().registerHints(hints, getClass().getClassLoader());
ReflectionHintsPredicates reflection = RuntimeHintsPredicates.reflection();
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class, "setConnectTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(
ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class, "setReadTimeout", int.class)))
.accepts(hints);
assertThat(reflection.onMethod(ReflectionUtils.findMethod(SimpleClientHttpRequestFactory.class,
"setBufferRequestBody", boolean.class))).accepts(hints);
}
private ClientHttpRequest createRequest(RestTemplate template) { private ClientHttpRequest createRequest(RestTemplate template) {
return ReflectionTestUtils.invokeMethod(template, "createRequest", URI.create("http://localhost"), return ReflectionTestUtils.invokeMethod(template, "createRequest", URI.create("http://localhost"),
HttpMethod.GET); HttpMethod.GET);
@ -654,4 +477,8 @@ class RestTemplateBuilderTests {
} }
static class TestHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
}
} }

@ -1,66 +0,0 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.client;
import java.time.Duration;
import okhttp3.OkHttpClient;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link RestTemplateBuilder} with OkHttp 3.x.
*
* @author Andy Wilkinson
*/
@ClassPathOverrides("com.squareup.okhttp3:okhttp:3.14.9")
class RestTemplateBuilderTestsOkHttp3Tests {
private RestTemplateBuilder builder = new RestTemplateBuilder();
@Test
void connectTimeoutCanBeConfiguredOnOkHttpRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setConnectTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).extracting("client", InstanceOfAssertFactories.type(OkHttpClient.class))
.extracting(OkHttpClient::connectTimeoutMillis).isEqualTo(1234);
}
@Test
void readTimeoutCanBeConfiguredOnOkHttpRequestFactory() {
ClientHttpRequestFactory requestFactory = this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setReadTimeout(Duration.ofMillis(1234)).build().getRequestFactory();
assertThat(requestFactory).extracting("client", InstanceOfAssertFactories.type(OkHttpClient.class))
.extracting(OkHttpClient::readTimeoutMillis).isEqualTo(1234);
}
@Test
void bufferRequestBodyCanNotBeConfiguredOnOkHttpRequestFactory() {
assertThatIllegalStateException()
.isThrownBy(() -> this.builder.requestFactory(OkHttp3ClientHttpRequestFactory.class)
.setBufferRequestBody(false).build().getRequestFactory())
.withMessageContaining(OkHttp3ClientHttpRequestFactory.class.getName());
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,8 +57,8 @@ class HttpWebServiceMessageSenderBuilderTests {
@Test @Test
void buildUsesHttpComponentsByDefault() { void buildUsesHttpComponentsByDefault() {
ClientHttpRequestMessageSender messageSender = build( ClientHttpRequestMessageSender messageSender = build(new HttpWebServiceMessageSenderBuilder()
new HttpWebServiceMessageSenderBuilder().setConnectTimeout(Duration.ofSeconds(5))); .setConnectTimeout(Duration.ofSeconds(5)).setReadTimeout(Duration.ofSeconds(5)));
ClientHttpRequestFactory requestFactory = messageSender.getRequestFactory(); ClientHttpRequestFactory requestFactory = messageSender.getRequestFactory();
assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); assertThat(requestFactory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

Loading…
Cancel
Save