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 0bdf8fd80ac6..cecdb4528e91 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. @@ -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 @@ public Consumer fromBundle(String bundleName) { @Override public Consumer 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); }; 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 f89fc31b6609..f50d99a271e3 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 @@ HttpMessageConvertersRestClientCustomizer httpMessageConvertersRestClientCustomi @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/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 000000000000..de15231c21e9 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/AutoConfiguredRestClientSslTests.java @@ -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 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> 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); + } + +} 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 fae799d96a97..251cf3c5690b 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.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 @@ void shouldSupplyBeans() { } @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