From 455bb0f8355e97aa1c1ed0048b198f1658d0ccee Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 3 Apr 2025 17:19:31 -0700 Subject: [PATCH] Add ClientHttpRequestFactoryBuilderCustomizer support Add `ClientHttpRequestFactoryBuilderCustomizer` interface that can be used to customize the `ClientHttpRequestFactoryBuilder`. Closes gh-44987 --- ...ntHttpRequestFactoryBuilderCustomizer.java | 38 +++++++++++++++++++ .../client/HttpClientAutoConfiguration.java | 18 ++++++++- .../HttpClientAutoConfigurationTests.java | 33 +++++++++++++++- .../reference/pages/io/rest-client.adoc | 2 +- 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/ClientHttpRequestFactoryBuilderCustomizer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/ClientHttpRequestFactoryBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/ClientHttpRequestFactoryBuilderCustomizer.java new file mode 100644 index 0000000000..a7b731842c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/ClientHttpRequestFactoryBuilderCustomizer.java @@ -0,0 +1,38 @@ +/* + * 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.http.client; + +import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; + +/** + * Customizer that can be used to modify the auto-configured + * {@link ClientHttpRequestFactoryBuilder} when its type matches. + * + * @param the builder type + * @author Phillip Webb + * @since 3.5.0 + */ +public interface ClientHttpRequestFactoryBuilderCustomizer> { + + /** + * Customize the given builder. + * @param builder the builder to customize + * @return the customized builder + */ + B customize(B builder); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java index 9d8d193fd1..58df283414 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.http.client; +import java.util.List; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -29,6 +31,7 @@ import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Red import org.springframework.boot.http.client.HttpClientSettings; import org.springframework.boot.http.client.HttpRedirects; import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.util.LambdaSafe; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.http.client.ClientHttpRequestFactory; @@ -48,8 +51,19 @@ public class HttpClientAutoConfiguration { @Bean @ConditionalOnMissingBean - ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties) { - return httpClientProperties.factoryBuilder(); + ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties, + ObjectProvider> clientHttpRequestFactoryBuilderCustomizers) { + ClientHttpRequestFactoryBuilder builder = httpClientProperties.factoryBuilder(); + return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList()); + } + + @SuppressWarnings("unchecked") + private ClientHttpRequestFactoryBuilder customize(ClientHttpRequestFactoryBuilder builder, + List> customizers) { + ClientHttpRequestFactoryBuilder[] builderReference = { builder }; + LambdaSafe.callbacks(ClientHttpRequestFactoryBuilderCustomizer.class, customizers, builderReference[0]) + .invoke((customizer) -> builderReference[0] = customizer.customize(builderReference[0])); + return builderReference[0]; } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java index 85a23fae4a..7f2ef917ad 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.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. @@ -27,9 +27,14 @@ import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; 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.http.client.HttpComponentsClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.JettyClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; import static org.assertj.core.api.Assertions.assertThat; @@ -89,4 +94,30 @@ class HttpClientAutoConfigurationTests { .doesNotHaveBean(ClientHttpRequestFactorySettings.class)); } + @Test + void clientHttpRequestFactoryBuilderCustomizersAreApplied() { + this.contextRunner.withUserConfiguration(ClientHttpRequestFactoryBuilderCustomizersConfiguration.class) + .run((context) -> { + ClientHttpRequestFactory factory = context.getBean(ClientHttpRequestFactoryBuilder.class).build(); + assertThat(factory).extracting("connectTimeout").isEqualTo(5L); + }); + } + + @Configuration(proxyBeanMethods = false) + static class ClientHttpRequestFactoryBuilderCustomizersConfiguration { + + @Bean + ClientHttpRequestFactoryBuilderCustomizer httpComponentsCustomizer() { + return (builder) -> builder.withCustomizer((factory) -> factory.setConnectTimeout(5)); + } + + @Bean + ClientHttpRequestFactoryBuilderCustomizer jettyCustomizer() { + return (builder) -> { + throw new IllegalStateException(); + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc index 207c7f1f6c..f9264f3343 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/io/rest-client.adoc @@ -272,7 +272,7 @@ spring: redirects: dont-follow ---- -For more complex customizations, you can declare your own javadoc:org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder[] bean which will cause auto-configuration to back off. +For more complex customizations, you can use javadoc:org.springframework.boot.autoconfigure.http.client.ClientHttpRequestFactoryBuilderCustomizer[] or declare your own javadoc:org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder[] bean which will cause auto-configuration to back off. This can be useful when you need to customize some of the internals of the underlying HTTP library. For example, the following will use a JDK client configured with a specific javadoc:java.net.ProxySelector[]: