diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java index 0bdf8fd80ac..5fc5c1052dc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSsl.java @@ -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. @@ -29,16 +29,20 @@ import org.springframework.web.client.RestClient; * An auto-configured {@link RestClientSsl} implementation. * * @author Phillip Webb + * @author Dmytro Nosan */ class AutoConfiguredRestClientSsl implements RestClientSsl { - private final ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder; + private final ClientHttpRequestFactoryBuilder builder; + + private final ClientHttpRequestFactorySettings settings; private final SslBundles sslBundles; AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder, - SslBundles sslBundles) { - this.clientHttpRequestFactoryBuilder = clientHttpRequestFactoryBuilder; + ClientHttpRequestFactorySettings clientHttpRequestFactorySettings, SslBundles sslBundles) { + this.builder = clientHttpRequestFactoryBuilder; + this.settings = clientHttpRequestFactorySettings; this.sslBundles = sslBundles; } @@ -49,11 +53,11 @@ class AutoConfiguredRestClientSsl implements RestClientSsl { @Override public Consumer fromBundle(SslBundle bundle) { - return (builder) -> { - ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(bundle); - ClientHttpRequestFactory requestFactory = this.clientHttpRequestFactoryBuilder.build(settings); - builder.requestFactory(requestFactory); - }; + return (builder) -> builder.requestFactory(requestFactory(bundle)); + } + + private ClientHttpRequestFactory requestFactory(SslBundle bundle) { + return this.builder.build(this.settings.withSslBundle(bundle)); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java index f89fc31b660..f50d99a271e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.java @@ -68,9 +68,12 @@ public class RestClientAutoConfiguration { @ConditionalOnMissingBean(RestClientSsl.class) @ConditionalOnBean(SslBundles.class) AutoConfiguredRestClientSsl restClientSsl( - ObjectProvider> clientHttpRequestFactoryBuilder, SslBundles sslBundles) { + ObjectProvider> clientHttpRequestFactoryBuilder, + ObjectProvider clientHttpRequestFactorySettings, SslBundles sslBundles) { return new AutoConfiguredRestClientSsl( - clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), sslBundles); + clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), + clientHttpRequestFactorySettings.getIfAvailable(ClientHttpRequestFactorySettings::defaults), + sslBundles); } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java index e7795beb8ce..2436fa1b228 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestClientSsl.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web.client; import java.util.function.Consumer; import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; import org.springframework.boot.ssl.NoSuchSslBundleException; import org.springframework.boot.ssl.SslBundle; import org.springframework.http.client.ClientHttpRequestFactory; @@ -34,8 +35,11 @@ import org.springframework.web.client.RestClient; * RestClient restClient = restClientBuilder.apply(ssl.fromBundle("mybundle")).build(); * return new MyBean(restClient); * } - * NOTE: Apply SSL configuration will replace any previously + * NOTE: Applying SSL configuration will replace any previously * {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}. + * The replacement {@link ClientHttpRequestFactory} will apply only configured + * {@link ClientHttpRequestFactorySettings} and the appropriate {@link SslBundle}. + *

* If you need to configure {@link ClientHttpRequestFactory} with more than just SSL * consider using a {@link ClientHttpRequestFactoryBuilder}. * diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSslTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSslTests.java new file mode 100644 index 00000000000..ec6b2b9932a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSslTests.java @@ -0,0 +1,95 @@ +/* + * 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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +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 + * @author Phillip Webb + */ +class AutoConfiguredRestClientSslTests { + + private final ClientHttpRequestFactorySettings settings = 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 factoryBuilder; + + @Mock + private ClientHttpRequestFactory factory; + + private AutoConfiguredRestClientSsl restClientSsl; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + this.restClientSsl = new AutoConfiguredRestClientSsl(this.factoryBuilder, this.settings, this.sslBundles); + } + + @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.factoryBuilder.build(this.settings.withSslBundle(sslBundle))).willReturn(this.factory); + RestClient restClient = build(this.restClientSsl.fromBundle(bundleName)); + assertThat(restClient).hasFieldOrPropertyWithValue("clientRequestFactory", this.factory); + } + + @Test + void shouldConfigureRestClientUsingBundle() { + SslBundle sslBundle = mock(SslBundle.class, "Custom SslBundle"); + given(this.factoryBuilder.build(this.settings.withSslBundle(sslBundle))).willReturn(this.factory); + RestClient restClient = build(this.restClientSsl.fromBundle(sslBundle)); + assertThat(restClient).hasFieldOrPropertyWithValue("clientRequestFactory", this.factory); + } + + private RestClient build(Consumer customizer) { + Builder builder = RestClient.builder(); + customizer.accept(builder); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java index fae799d96a9..b114f52f11f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfigurationTests.java @@ -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; @@ -50,6 +52,7 @@ import static org.mockito.Mockito.mock; * * @author Arjen Poutsma * @author Moritz Halbritter + * @author Dmytro Nosan */ class RestClientAutoConfigurationTests { @@ -66,9 +69,41 @@ 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("builder", clientHttpRequestFactoryBuilder); + assertThat(restClientSsl).hasFieldOrPropertyWithValue("settings", 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("builder", + context.getBean(ClientHttpRequestFactoryBuilder.class)); + assertThat(restClientSsl).hasFieldOrPropertyWithValue("settings", + context.getBean(ClientHttpRequestFactorySettings.class)); + }); } @Test