Skip to content

Commit 7817797

Browse files
committed
Drop custom CustomHttpComponentsClientHttpRequestFactory
Replace `CustomHttpComponentsClientHttpRequestFactory` with `HttpComponentsClientHttpRequestFactoryBuilder` to reduce the amount of duplicated logic. Closes gh-43422
1 parent a572283 commit 7817797

File tree

3 files changed

+124
-31
lines changed

3 files changed

+124
-31
lines changed

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

+64-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.Map;
2626
import java.util.Set;
2727
import java.util.concurrent.TimeUnit;
28+
import java.util.function.Consumer;
29+
import java.util.function.Function;
2830
import java.util.function.UnaryOperator;
2931

3032
import javax.net.ssl.SSLContext;
@@ -45,8 +47,11 @@
4547
import org.apache.hc.core5.ssl.SSLContextBuilder;
4648
import org.apache.hc.core5.ssl.TrustStrategy;
4749

50+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
4851
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
4952
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
53+
import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder;
54+
import org.springframework.boot.ssl.SslBundle;
5055
import org.springframework.boot.web.client.RestTemplateBuilder;
5156
import org.springframework.boot.web.client.RootUriTemplateHandler;
5257
import org.springframework.core.ParameterizedTypeReference;
@@ -56,7 +61,6 @@
5661
import org.springframework.http.RequestEntity;
5762
import org.springframework.http.RequestEntity.UriTemplateRequestEntity;
5863
import org.springframework.http.ResponseEntity;
59-
import org.springframework.http.client.ClientHttpRequestFactory;
6064
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
6165
import org.springframework.util.Assert;
6266
import org.springframework.util.ObjectUtils;
@@ -155,21 +159,29 @@ private TestRestTemplate(RestTemplateBuilder builder, UriTemplateHandler uriTemp
155159
private static RestTemplateBuilder createInitialBuilder(RestTemplateBuilder builder, String username,
156160
String password, HttpClientOption... httpClientOptions) {
157161
Assert.notNull(builder, "'builder' must not be null");
158-
if (httpClientOptions != null) {
159-
ClientHttpRequestFactory requestFactory = builder.buildRequestFactory();
160-
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
161-
builder = builder.redirects(HttpClientOption.ENABLE_REDIRECTS.isPresent(httpClientOptions)
162-
? Redirects.FOLLOW : Redirects.DONT_FOLLOW);
163-
builder = builder.requestFactoryBuilder(
164-
(settings) -> new CustomHttpComponentsClientHttpRequestFactory(httpClientOptions, settings));
165-
}
162+
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder = builder.requestFactoryBuilder();
163+
if (requestFactoryBuilder instanceof HttpComponentsClientHttpRequestFactoryBuilder) {
164+
builder = builder.requestFactoryBuilder(applyHttpClientOptions(
165+
(HttpComponentsClientHttpRequestFactoryBuilder) requestFactoryBuilder, httpClientOptions));
166+
builder = builder.redirects(HttpClientOption.ENABLE_REDIRECTS.isPresent(httpClientOptions)
167+
? Redirects.FOLLOW : Redirects.DONT_FOLLOW);
166168
}
167169
if (username != null || password != null) {
168170
builder = builder.basicAuthentication(username, password);
169171
}
170172
return builder;
171173
}
172174

175+
private static HttpComponentsClientHttpRequestFactoryBuilder applyHttpClientOptions(
176+
HttpComponentsClientHttpRequestFactoryBuilder builder, HttpClientOption[] httpClientOptions) {
177+
builder = builder.withDefaultRequestConfigCustomizer(
178+
new CookieSpecCustomizer(HttpClientOption.ENABLE_COOKIES.isPresent(httpClientOptions)));
179+
if (HttpClientOption.SSL.isPresent(httpClientOptions)) {
180+
builder = builder.withTlsSocketStrategyFactory(new SelfSignedTlsSocketStrategyFactory());
181+
}
182+
return builder;
183+
}
184+
173185
/**
174186
* Configure the {@link UriTemplateHandler} to use to expand URI templates. By default
175187
* the {@link DefaultUriBuilderFactory} is used which relies on Spring's URI template
@@ -1049,7 +1061,10 @@ boolean isPresent(HttpClientOption[] options) {
10491061

10501062
/**
10511063
* {@link HttpComponentsClientHttpRequestFactory} to apply customizations.
1064+
*
1065+
* @deprecated since 3.5.0 for removal in 4.0.0
10521066
*/
1067+
@Deprecated
10531068
protected static class CustomHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
10541069

10551070
private final String cookieSpec;
@@ -1142,6 +1157,31 @@ protected RequestConfig createRequestConfig() {
11421157

11431158
}
11441159

1160+
/**
1161+
* Factory used to create a {@link TlsSocketStrategy} supporting self-signed
1162+
* certificates.
1163+
*/
1164+
private static class SelfSignedTlsSocketStrategyFactory implements Function<SslBundle, TlsSocketStrategy> {
1165+
1166+
private static final String[] SUPPORTED_PROTOCOLS = { TLS.V_1_3.getId(), TLS.V_1_2.getId() };
1167+
1168+
@Override
1169+
public TlsSocketStrategy apply(SslBundle sslBundle) {
1170+
try {
1171+
TrustSelfSignedStrategy trustStrategy = new TrustSelfSignedStrategy();
1172+
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build();
1173+
return new DefaultClientTlsStrategy(sslContext, SUPPORTED_PROTOCOLS, null, null, null);
1174+
}
1175+
catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException ex) {
1176+
throw new IllegalStateException(ex);
1177+
}
1178+
}
1179+
1180+
}
1181+
1182+
/**
1183+
* {@link TrustStrategy} supporting self-signed certificates.
1184+
*/
11451185
private static final class TrustSelfSignedStrategy implements TrustStrategy {
11461186

11471187
@Override
@@ -1151,4 +1191,19 @@ public boolean isTrusted(X509Certificate[] chain, String authType) {
11511191

11521192
}
11531193

1194+
private static class CookieSpecCustomizer implements Consumer<RequestConfig.Builder> {
1195+
1196+
private final boolean enableCookies;
1197+
1198+
CookieSpecCustomizer(boolean enableCookies) {
1199+
this.enableCookies = enableCookies;
1200+
}
1201+
1202+
@Override
1203+
public void accept(RequestConfig.Builder builder) {
1204+
builder.setCookieSpec(this.enableCookies ? StandardCookieSpec.STRICT : StandardCookieSpec.IGNORE);
1205+
}
1206+
1207+
}
1208+
11541209
}

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

+47-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,12 +26,15 @@
2626
import java.util.stream.Stream;
2727

2828
import org.apache.hc.client5.http.config.RequestConfig;
29+
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
30+
import org.apache.hc.client5.http.impl.classic.RedirectExec;
31+
import org.apache.hc.client5.http.protocol.RedirectStrategy;
32+
import org.assertj.core.extractor.Extractors;
2933
import org.junit.jupiter.api.Test;
3034

3135
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
3236
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
3337
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
34-
import org.springframework.boot.test.web.client.TestRestTemplate.CustomHttpComponentsClientHttpRequestFactory;
3538
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
3639
import org.springframework.boot.web.client.RestTemplateBuilder;
3740
import org.springframework.core.ParameterizedTypeReference;
@@ -137,6 +140,7 @@ void authenticated() {
137140
}
138141

139142
@Test
143+
@SuppressWarnings("removal")
140144
void options() {
141145
RequestConfig config = getRequestConfig(
142146
new TestRestTemplate(HttpClientOption.ENABLE_REDIRECTS, HttpClientOption.ENABLE_COOKIES));
@@ -155,25 +159,29 @@ void jdkBuilderCanBeSpecifiedWithSpecificRedirects() {
155159
}
156160

157161
@Test
162+
@SuppressWarnings("removal")
158163
void httpComponentsAreBuiltConsideringSettingsInRestTemplateBuilder() {
159164
RestTemplateBuilder builder = new RestTemplateBuilder()
160165
.requestFactoryBuilder(ClientHttpRequestFactoryBuilder.httpComponents());
161-
assertThat(getRequestConfig((RestTemplateBuilder) null).isRedirectsEnabled()).isFalse();
162-
assertThat(getRequestConfig(null, HttpClientOption.ENABLE_REDIRECTS).isRedirectsEnabled()).isTrue();
163-
assertThat(getRequestConfig(builder).isRedirectsEnabled()).isFalse();
164-
assertThat(getRequestConfig(builder, HttpClientOption.ENABLE_REDIRECTS).isRedirectsEnabled()).isTrue();
165-
assertThat(getRequestConfig(builder.redirects(Redirects.DONT_FOLLOW)).isRedirectsEnabled()).isFalse();
166-
assertThat(getRequestConfig(builder.redirects(Redirects.DONT_FOLLOW), HttpClientOption.ENABLE_REDIRECTS)
167-
.isRedirectsEnabled()).isTrue();
166+
assertThat(getRedirectStrategy((RestTemplateBuilder) null)).matches(this::isDontFollowStrategy);
167+
assertThat(getRedirectStrategy(null, HttpClientOption.ENABLE_REDIRECTS)).matches(this::isFollowStrategy);
168+
assertThat(getRedirectStrategy(builder)).matches(this::isDontFollowStrategy);
169+
assertThat(getRedirectStrategy(builder, HttpClientOption.ENABLE_REDIRECTS)).matches(this::isFollowStrategy);
170+
assertThat(getRedirectStrategy(builder.redirects(Redirects.DONT_FOLLOW))).matches(this::isDontFollowStrategy);
171+
assertThat(getRedirectStrategy(builder.redirects(Redirects.DONT_FOLLOW), HttpClientOption.ENABLE_REDIRECTS))
172+
.matches(this::isFollowStrategy);
168173
}
169174

170175
@Test
171176
void withRequestFactorySettingsRedirectsForHttpComponents() {
172177
TestRestTemplate template = new TestRestTemplate();
173-
assertThat(getRequestConfig(template).isRedirectsEnabled()).isFalse();
174-
assertThat(getRequestConfig(template
175-
.withRequestFactorySettings(ClientHttpRequestFactorySettings.defaults().withRedirects(Redirects.FOLLOW)))
176-
.isRedirectsEnabled()).isTrue();
178+
assertThat(getRedirectStrategy(template)).matches(this::isDontFollowStrategy);
179+
assertThat(getRedirectStrategy(template
180+
.withRequestFactorySettings(ClientHttpRequestFactorySettings.defaults().withRedirects(Redirects.FOLLOW))))
181+
.matches(this::isFollowStrategy);
182+
assertThat(getRedirectStrategy(template.withRequestFactorySettings(
183+
ClientHttpRequestFactorySettings.defaults().withRedirects(Redirects.DONT_FOLLOW))))
184+
.matches(this::isDontFollowStrategy);
177185
}
178186

179187
@Test
@@ -196,17 +204,36 @@ void withRequestFactorySettingsUpdateRedirectsForJdk() {
196204
.followRedirects()).isEqualTo(Redirect.NEVER);
197205
}
198206

199-
private RequestConfig getRequestConfig(RestTemplateBuilder builder, HttpClientOption... httpClientOptions) {
207+
private RequestConfig getRequestConfig(TestRestTemplate template) {
208+
ClientHttpRequestFactory requestFactory = template.getRestTemplate().getRequestFactory();
209+
return (RequestConfig) Extractors.byName("httpClient.defaultConfig").apply(requestFactory);
210+
}
211+
212+
private RedirectStrategy getRedirectStrategy(RestTemplateBuilder builder, HttpClientOption... httpClientOptions) {
200213
builder = (builder != null) ? builder : new RestTemplateBuilder();
201214
TestRestTemplate template = new TestRestTemplate(builder, null, null, httpClientOptions);
202-
return getRequestConfig(template);
215+
return getRedirectStrategy(template);
203216
}
204217

205-
private RequestConfig getRequestConfig(TestRestTemplate template) {
206-
CustomHttpComponentsClientHttpRequestFactory factory = (CustomHttpComponentsClientHttpRequestFactory) template
207-
.getRestTemplate()
208-
.getRequestFactory();
209-
return factory.createRequestConfig();
218+
private RedirectStrategy getRedirectStrategy(TestRestTemplate template) {
219+
ClientHttpRequestFactory requestFactory = template.getRestTemplate().getRequestFactory();
220+
Object chain = Extractors.byName("httpClient.execChain").apply(requestFactory);
221+
while (chain != null) {
222+
Object handler = Extractors.byName("handler").apply(chain);
223+
if (handler instanceof RedirectExec) {
224+
return (RedirectStrategy) Extractors.byName("redirectStrategy").apply(handler);
225+
}
226+
chain = Extractors.byName("next").apply(chain);
227+
}
228+
return null;
229+
}
230+
231+
private boolean isFollowStrategy(RedirectStrategy redirectStrategy) {
232+
return redirectStrategy instanceof DefaultRedirectStrategy;
233+
}
234+
235+
private boolean isDontFollowStrategy(RedirectStrategy redirectStrategy) {
236+
return redirectStrategy.getClass().getName().contains("NoFollow");
210237
}
211238

212239
private HttpClient getJdkHttpClient(TestRestTemplate template) {

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RestTemplateBuilder.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -750,11 +750,22 @@ public <T extends RestTemplate> T configure(T restTemplate) {
750750
* @since 2.2.0
751751
*/
752752
public ClientHttpRequestFactory buildRequestFactory() {
753+
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder = requestFactoryBuilder();
754+
return (requestFactoryBuilder != null) ? requestFactoryBuilder.build(this.requestFactorySettings) : null;
755+
}
756+
757+
/**
758+
* Return a {@link ClientHttpRequestFactoryBuilder} instance using the settings of
759+
* this builder.
760+
* @return a {@link ClientHttpRequestFactoryBuilder} or {@code null}
761+
* @since 3.5.0
762+
*/
763+
public ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder() {
753764
if (this.requestFactoryBuilder != null) {
754-
return this.requestFactoryBuilder.build(this.requestFactorySettings);
765+
return this.requestFactoryBuilder;
755766
}
756767
if (this.detectRequestFactory) {
757-
return ClientHttpRequestFactoryBuilder.detect().build(this.requestFactorySettings);
768+
return ClientHttpRequestFactoryBuilder.detect();
758769
}
759770
return null;
760771
}

0 commit comments

Comments
 (0)