Update RestClientSsl to support ClientHttpRequestFactorySettings

Prior to this commit, RestClientSsl always used the
default settings from ClientHttpRequestFactorySettings, overriding any
user-defined settings (e.g., HttpClientProperties).

With this commit, RestClientSsl now respects and uses
ClientHttpRequestFactorySettings when they are provided.

See gh-44979

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan
2025-04-02 18:12:07 +03:00
committed by Phillip Webb
parent 6746cac514
commit af5d72b94f
4 changed files with 152 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@@ -34,11 +34,14 @@ class AutoConfiguredRestClientSsl implements RestClientSsl {
private final ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder;
private final ClientHttpRequestFactorySettings clientHttpRequestFactorySettings;
private final SslBundles sslBundles;
AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder,
SslBundles sslBundles) {
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings, SslBundles sslBundles) {
this.clientHttpRequestFactoryBuilder = clientHttpRequestFactoryBuilder;
this.clientHttpRequestFactorySettings = clientHttpRequestFactorySettings;
this.sslBundles = sslBundles;
}
@@ -50,7 +53,7 @@ class AutoConfiguredRestClientSsl implements RestClientSsl {
@Override
public Consumer<RestClient.Builder> fromBundle(SslBundle bundle) {
return (builder) -> {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(bundle);
ClientHttpRequestFactorySettings settings = this.clientHttpRequestFactorySettings.withSslBundle(bundle);
ClientHttpRequestFactory requestFactory = this.clientHttpRequestFactoryBuilder.build(settings);
builder.requestFactory(requestFactory);
};

View File

@@ -68,9 +68,12 @@ public class RestClientAutoConfiguration {
@ConditionalOnMissingBean(RestClientSsl.class)
@ConditionalOnBean(SslBundles.class)
AutoConfiguredRestClientSsl restClientSsl(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder, SslBundles sslBundles) {
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings, SslBundles sslBundles) {
return new AutoConfiguredRestClientSsl(
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), sslBundles);
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults),
sslBundles);
}
@Bean

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2012-2025 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.autoconfigure.web.client;
import java.time.Duration;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.Builder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AutoConfiguredRestClientSsl}.
*
* @author Dmytro Nosan
*/
@ExtendWith(MockitoExtension.class)
class AutoConfiguredRestClientSslTests {
private final ClientHttpRequestFactorySettings clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings
.ofSslBundle(mock(SslBundle.class, "Default SslBundle"))
.withRedirects(Redirects.DONT_FOLLOW)
.withReadTimeout(Duration.ofSeconds(10))
.withConnectTimeout(Duration.ofSeconds(30));
@Mock
private SslBundles sslBundles;
@Mock
private ClientHttpRequestFactoryBuilder<ClientHttpRequestFactory> clientHttpRequestFactoryBuilder;
@Mock
private ClientHttpRequestFactory clientHttpRequestFactory;
@Test
void shouldConfigureRestClientUsingBundleName() {
String bundleName = "test";
SslBundle sslBundle = mock(SslBundle.class, "SslBundle named '%s'".formatted(bundleName));
given(this.sslBundles.getBundle(bundleName)).willReturn(sslBundle);
given(this.clientHttpRequestFactoryBuilder
.build(this.clientHttpRequestFactorySettings.withSslBundle(sslBundle)))
.willReturn(this.clientHttpRequestFactory);
assertThat(applySslBundle((restClientSsl) -> restClientSsl.fromBundle(bundleName)))
.hasFieldOrPropertyWithValue("clientRequestFactory", this.clientHttpRequestFactory);
}
@Test
void shouldConfigureRestClientUsingBundle() {
SslBundle sslBundle = mock(SslBundle.class, "Custom SslBundle");
given(this.clientHttpRequestFactoryBuilder
.build(this.clientHttpRequestFactorySettings.withSslBundle(sslBundle)))
.willReturn(this.clientHttpRequestFactory);
assertThat(applySslBundle((restClientSsl) -> restClientSsl.fromBundle(sslBundle)))
.hasFieldOrPropertyWithValue("clientRequestFactory", this.clientHttpRequestFactory);
}
private RestClient applySslBundle(Function<RestClientSsl, Consumer<Builder>> applySslBundle) {
Builder builder = RestClient.builder();
applySslBundle.apply(getRestClientSsl()).accept(builder);
return builder.build();
}
private RestClientSsl getRestClientSsl() {
return new AutoConfiguredRestClientSsl(this.clientHttpRequestFactoryBuilder,
this.clientHttpRequestFactorySettings, this.sslBundles);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web.client;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -27,6 +28,7 @@ import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfigur
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.client.RestClientCustomizer;
@@ -66,9 +68,43 @@ class RestClientAutoConfigurationTests {
}
@Test
void shouldSupplyRestClientSslIfSslBundlesIsThere() {
this.contextRunner.withBean(SslBundles.class, () -> mock(SslBundles.class))
.run((context) -> assertThat(context).hasSingleBean(RestClientSsl.class));
void shouldSupplyRestClientSslIfSslBundlesIsThereWithCustomHttpSettingsAndBuilder() {
SslBundles sslBundles = mock(SslBundles.class);
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings.defaults()
.withRedirects(Redirects.DONT_FOLLOW)
.withConnectTimeout(Duration.ofHours(1))
.withReadTimeout(Duration.ofDays(1))
.withSslBundle(mock(SslBundle.class));
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder = mock(
ClientHttpRequestFactoryBuilder.class);
this.contextRunner.withBean(SslBundles.class, () -> sslBundles)
.withBean(ClientHttpRequestFactorySettings.class, () -> clientHttpRequestFactorySettings)
.withBean(ClientHttpRequestFactoryBuilder.class, () -> clientHttpRequestFactoryBuilder)
.run((context) -> {
assertThat(context).hasSingleBean(RestClientSsl.class);
RestClientSsl restClientSsl = context.getBean(RestClientSsl.class);
assertThat(restClientSsl).hasFieldOrPropertyWithValue("sslBundles", sslBundles);
assertThat(restClientSsl).hasFieldOrPropertyWithValue("clientHttpRequestFactoryBuilder",
clientHttpRequestFactoryBuilder);
assertThat(restClientSsl).hasFieldOrPropertyWithValue("clientHttpRequestFactorySettings",
clientHttpRequestFactorySettings);
});
}
@Test
void shouldSupplyRestClientSslIfSslBundlesIsThereWithAutoConfiguredHttpSettingsAndBuilder() {
SslBundles sslBundles = mock(SslBundles.class);
this.contextRunner.withBean(SslBundles.class, () -> sslBundles).run((context) -> {
assertThat(context).hasSingleBean(RestClientSsl.class)
.hasSingleBean(ClientHttpRequestFactorySettings.class)
.hasSingleBean(ClientHttpRequestFactoryBuilder.class);
RestClientSsl restClientSsl = context.getBean(RestClientSsl.class);
assertThat(restClientSsl).hasFieldOrPropertyWithValue("sslBundles", sslBundles);
assertThat(restClientSsl).hasFieldOrPropertyWithValue("clientHttpRequestFactoryBuilder",
context.getBean(ClientHttpRequestFactoryBuilder.class));
assertThat(restClientSsl).hasFieldOrPropertyWithValue("clientHttpRequestFactorySettings",
context.getBean(ClientHttpRequestFactorySettings.class));
});
}
@Test