Add `HttpClientAutoConfiguration` and use it wherever possible

Add a new `HttpClientAutoConfiguration` class that provides
`ClientHttpRequestFactoryBuilder` and `ClientHttpRequestFactorySettings`
beans and new configuration properties.

The existing `RestTemplate`, `RestClient` and `WebServiceTemplate`
auto-configurations have been updated to make use of the new
HTTP client support.

Users may now set `spring.http.client` property to globally change
the `ClientHttpRequestFactory` used in their application.

Closes gh-36266
This commit is contained in:
Phillip Webb 2024-10-23 23:57:13 -07:00
parent 6356e904fc
commit 3a8b2e4bc8
21 changed files with 600 additions and 46 deletions

View File

@ -0,0 +1,70 @@
/*
* Copyright 2012-2024 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.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link ClientHttpRequestFactoryBuilder} and {@link ClientHttpRequestFactorySettings}.
*
* @author Phillip Webb
* @since 3.4.0
*/
@AutoConfiguration(after = SslAutoConfiguration.class)
@ConditionalOnClass(ClientHttpRequestFactory.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties) {
Factory factory = httpClientProperties.getFactory();
return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect();
}
@Bean
@ConditionalOnMissingBean
ClientHttpRequestFactorySettings clientHttpRequestFactorySettings(HttpClientProperties httpClientProperties,
ObjectProvider<SslBundles> sslBundles) {
SslBundle sslBundle = getSslBundle(httpClientProperties.getSsl(), sslBundles);
return new ClientHttpRequestFactorySettings(httpClientProperties.getConnectTimeout(),
httpClientProperties.getReadTimeout(), sslBundle);
}
private SslBundle getSslBundle(HttpClientProperties.Ssl properties, ObjectProvider<SslBundles> sslBundles) {
String name = properties.getBundle();
return (StringUtils.hasLength(name)) ? sslBundles.getObject().getBundle(name) : null;
}
}

View File

@ -0,0 +1,145 @@
/*
* Copyright 2012-2024 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 java.time.Duration;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for a Spring's blocking HTTP
* clients.
*
* @author Phillip Webb
* @since 3.4.0
*/
@ConfigurationProperties("spring.http.client")
public class HttpClientProperties {
/**
* Default factory used for a client HTTP request.
*/
private Factory factory;
/**
* Default connect timeout for a client HTTP request.
*/
private Duration connectTimeout;
/**
* Default read timeout for a client HTTP request.
*/
private Duration readTimeout;
/**
* Default SSL configuration for a client HTTP request.
*/
private Ssl ssl = new Ssl();
public Factory getFactory() {
return this.factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
public Duration getConnectTimeout() {
return this.connectTimeout;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
public Ssl getSsl() {
return this.ssl;
}
/**
* Supported factory types.
*/
public enum Factory {
/**
* Apache HttpComponents HttpClient.
*/
HTTP_COMPONENTS(ClientHttpRequestFactoryBuilder::httpComponents),
/**
* Jetty's HttpClient.
*/
JETTY(ClientHttpRequestFactoryBuilder::jetty),
/**
* Reactor-Netty.
*/
REACTOR(ClientHttpRequestFactoryBuilder::reactor),
/**
* Java's HttpClient.
*/
JDK(ClientHttpRequestFactoryBuilder::jdk),
/**
* Standard JDK facilities.
*/
SIMPLE(ClientHttpRequestFactoryBuilder::simple);
private final Supplier<ClientHttpRequestFactoryBuilder<?>> builderSupplier;
Factory(Supplier<ClientHttpRequestFactoryBuilder<?>> builderSupplier) {
this.builderSupplier = builderSupplier;
}
ClientHttpRequestFactoryBuilder<?> builder() {
return this.builderSupplier.get();
}
}
/**
* SSL configuration.
*/
public static class Ssl {
/**
* SSL bundle to use.
*/
private String bundle;
public String getBundle() {
return this.bundle;
}
public void setBundle(String bundle) {
this.bundle = bundle;
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2012-2024 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.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
/**
* {@link SpringBootCondition} that applies only when running in a non-reactive web
* application.
*
* @author Phillip Webb
*/
class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
private static final class ReactiveWebApplication {
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2012-2024 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.
*/
/**
* Auto-configuration for client-side HTTP.
*/
package org.springframework.boot.autoconfigure.http.client;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -18,10 +18,10 @@ 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.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
@ -32,9 +32,13 @@ import org.springframework.web.client.RestClient;
*/
class AutoConfiguredRestClientSsl implements RestClientSsl {
private final ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder;
private final SslBundles sslBundles;
AutoConfiguredRestClientSsl(SslBundles sslBundles) {
AutoConfiguredRestClientSsl(ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder,
SslBundles sslBundles) {
this.clientHttpRequestFactoryBuilder = clientHttpRequestFactoryBuilder;
this.sslBundles = sslBundles;
}
@ -46,8 +50,8 @@ class AutoConfiguredRestClientSsl implements RestClientSsl {
@Override
public Consumer<RestClient.Builder> fromBundle(SslBundle bundle) {
return (builder) -> {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS.withSslBundle(bundle);
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(bundle);
ClientHttpRequestFactory requestFactory = this.clientHttpRequestFactoryBuilder.build(settings);
builder.requestFactory(requestFactory);
};
}

View File

@ -24,10 +24,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
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.ssl.SslBundles;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@ -48,7 +49,8 @@ import org.springframework.web.client.RestClient.Builder;
* @author Moritz Halbritter
* @since 3.2.0
*/
@AutoConfiguration(after = { HttpMessageConvertersAutoConfiguration.class, SslAutoConfiguration.class })
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
SslAutoConfiguration.class })
@ConditionalOnClass(RestClient.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestClientAutoConfiguration {
@ -64,14 +66,21 @@ public class RestClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RestClientSsl.class)
@ConditionalOnBean(SslBundles.class)
AutoConfiguredRestClientSsl restClientSsl(SslBundles sslBundles) {
return new AutoConfiguredRestClientSsl(sslBundles);
AutoConfiguredRestClientSsl restClientSsl(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder, SslBundles sslBundles) {
return new AutoConfiguredRestClientSsl(
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect), sslBundles);
}
@Bean
@ConditionalOnMissingBean
RestClientBuilderConfigurer restClientBuilderConfigurer(ObjectProvider<RestClientCustomizer> customizerProvider) {
RestClientBuilderConfigurer restClientBuilderConfigurer(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
ObjectProvider<RestClientCustomizer> customizerProvider) {
RestClientBuilderConfigurer configurer = new RestClientBuilderConfigurer();
configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable());
configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable());
configurer.setRestClientCustomizers(customizerProvider.orderedStream().toList());
return configurer;
}
@ -80,9 +89,7 @@ public class RestClientAutoConfiguration {
@Scope("prototype")
@ConditionalOnMissingBean
RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
RestClient.Builder builder = RestClient.builder()
.requestFactory(ClientHttpRequestFactories.get(ClientHttpRequestFactorySettings.DEFAULTS));
return restClientBuilderConfigurer.configure(builder);
return restClientBuilderConfigurer.configure(RestClient.builder());
}
}

View File

@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.web.client;
import java.util.List;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.Builder;
@ -30,8 +32,20 @@ import org.springframework.web.client.RestClient.Builder;
*/
public class RestClientBuilderConfigurer {
private ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
private ClientHttpRequestFactorySettings requestFactorySettings;
private List<RestClientCustomizer> customizers;
void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
this.requestFactoryBuilder = requestFactoryBuilder;
}
void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
this.requestFactorySettings = requestFactorySettings;
}
void setRestClientCustomizers(List<RestClientCustomizer> customizers) {
this.customizers = customizers;
}
@ -43,6 +57,9 @@ public class RestClientBuilderConfigurer {
* @return the configured builder
*/
public RestClient.Builder configure(RestClient.Builder builder) {
ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder = (this.requestFactoryBuilder != null)
? this.requestFactoryBuilder : ClientHttpRequestFactoryBuilder.detect();
builder.requestFactory(requestFactoryBuilder.build(this.requestFactorySettings));
applyCustomizers(builder);
return builder;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -18,10 +18,9 @@ package org.springframework.boot.autoconfigure.web.client;
import java.util.function.Consumer;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.web.client.ClientHttpRequestFactories;
import org.springframework.boot.web.client.ClientHttpRequestFactorySettings;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
@ -38,8 +37,7 @@ import org.springframework.web.client.RestClient;
* </pre> NOTE: Apply SSL configuration will replace any previously
* {@link RestClient.Builder#requestFactory configured} {@link ClientHttpRequestFactory}.
* If you need to configure {@link ClientHttpRequestFactory} with more than just SSL
* consider using a {@link ClientHttpRequestFactorySettings} with
* {@link ClientHttpRequestFactories}.
* consider using a {@link ClientHttpRequestFactoryBuilder}.
*
* @author Phillip Webb
* @since 3.2.0

View File

@ -23,6 +23,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
@ -32,13 +35,14 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.web.client.RestTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate}.
* {@link EnableAutoConfiguration Auto-configuration} for {@link RestTemplate} (via
* {@link RestTemplateBuilder}).
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.4.0
*/
@AutoConfiguration(after = HttpMessageConvertersAutoConfiguration.class)
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
@ConditionalOnClass(RestTemplate.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestTemplateAutoConfiguration {
@ -46,10 +50,14 @@ public class RestTemplateAutoConfiguration {
@Bean
@Lazy
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
configurer.setRequestFactoryBuilder(clientHttpRequestFactoryBuilder.getIfAvailable());
configurer.setRequestFactorySettings(clientHttpRequestFactorySettings.getIfAvailable());
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().toList());
configurer.setRestTemplateRequestCustomizers(restTemplateRequestCustomizers.orderedStream().toList());
@ -60,8 +68,7 @@ public class RestTemplateAutoConfiguration {
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
return restTemplateBuilderConfigurer.configure(new RestTemplateBuilder());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2024 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.
@ -21,6 +21,8 @@ import java.util.List;
import java.util.function.BiFunction;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.boot.web.client.RestTemplateRequestCustomizer;
@ -34,12 +36,24 @@ import org.springframework.util.ObjectUtils;
*/
public final class RestTemplateBuilderConfigurer {
private ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder;
private ClientHttpRequestFactorySettings requestFactorySettings;
private HttpMessageConverters httpMessageConverters;
private List<RestTemplateCustomizer> restTemplateCustomizers;
private List<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers;
void setRequestFactoryBuilder(ClientHttpRequestFactoryBuilder<?> requestFactoryBuilder) {
this.requestFactoryBuilder = requestFactoryBuilder;
}
void setRequestFactorySettings(ClientHttpRequestFactorySettings requestFactorySettings) {
this.requestFactorySettings = requestFactorySettings;
}
void setHttpMessageConverters(HttpMessageConverters httpMessageConverters) {
this.httpMessageConverters = httpMessageConverters;
}
@ -59,6 +73,12 @@ public final class RestTemplateBuilderConfigurer {
* @return the configured builder
*/
public RestTemplateBuilder configure(RestTemplateBuilder builder) {
if (this.requestFactoryBuilder != null) {
builder = builder.requestFactoryBuilder(this.requestFactoryBuilder);
}
if (this.requestFactorySettings != null) {
builder = builder.requestFactorySettings(this.requestFactorySettings);
}
if (this.httpMessageConverters != null) {
builder = builder.messageConverters(this.httpMessageConverters.getConverters());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -23,6 +23,10 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.webservices.client.WebServiceMessageSenderFactory;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.boot.webservices.client.WebServiceTemplateCustomizer;
import org.springframework.context.annotation.Bean;
@ -36,20 +40,35 @@ import org.springframework.ws.client.core.WebServiceTemplate;
* @author Dmytro Nosan
* @since 2.1.0
*/
@AutoConfiguration
@AutoConfiguration(after = HttpClientAutoConfiguration.class)
@ConditionalOnClass({ WebServiceTemplate.class, Unmarshaller.class, Marshaller.class })
public class WebServiceTemplateAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebServiceMessageSenderFactory webServiceHttpMessageSenderFactory(
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientHttpRequestFactoryBuilder,
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings) {
return WebServiceMessageSenderFactory.http(
clientHttpRequestFactoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect),
clientHttpRequestFactorySettings.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean
public WebServiceTemplateBuilder webServiceTemplateBuilder(
ObjectProvider<WebServiceMessageSenderFactory> httpWebServiceMessageSenderBuilder,
ObjectProvider<WebServiceTemplateCustomizer> webServiceTemplateCustomizers) {
WebServiceTemplateBuilder builder = new WebServiceTemplateBuilder();
WebServiceTemplateBuilder templateBuilder = new WebServiceTemplateBuilder();
WebServiceMessageSenderFactory httpMessageSenderFactory = httpWebServiceMessageSenderBuilder.getIfAvailable();
if (httpMessageSenderFactory != null) {
templateBuilder = templateBuilder.httpMessageSenderFactory(httpMessageSenderFactory);
}
List<WebServiceTemplateCustomizer> customizers = webServiceTemplateCustomizers.orderedStream().toList();
if (!customizers.isEmpty()) {
builder = builder.customizers(customizers);
templateBuilder = templateBuilder.customizers(customizers);
}
return builder;
return templateBuilder;
}
}

View File

@ -62,6 +62,7 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration

View File

@ -0,0 +1,90 @@
/*
* Copyright 2012-2024 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 java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
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.SimpleClientHttpRequestFactoryBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpClientAutoConfiguration}.
*
* @author Phillip Webb
*/
class HttpClientAutoConfigurationTests {
private static final AutoConfigurations autoConfigurations = AutoConfigurations
.of(HttpClientAutoConfiguration.class, SslAutoConfiguration.class);
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(autoConfigurations);
@Test
void configuresDetectedClientHttpRequestFactoryBuilder() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ClientHttpRequestFactoryBuilder.class));
}
@Test
void configuresDefinedClientHttpRequestFactoryBuilder() {
this.contextRunner.withPropertyValues("spring.http.client.factory=simple")
.run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class))
.isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class));
}
@Test
void configuresClientHttpRequestFactorySettings() {
this.contextRunner.withPropertyValues(sslPropertyValues().toArray(String[]::new))
.withPropertyValues("spring.http.client.connect-timeout=10s", "spring.http.client.read-timeout=20s",
"spring.http.client.ssl.bundle=test")
.run((context) -> {
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(10));
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(20));
assertThat(settings.sslBundle().getKey().getAlias()).isEqualTo("alias1");
});
}
private List<String> sslPropertyValues() {
List<String> propertyValues = new ArrayList<>();
String location = "classpath:org/springframework/boot/autoconfigure/ssl/";
propertyValues.add("spring.ssl.bundle.pem.test.key.alias=alias1");
propertyValues.add("spring.ssl.bundle.pem.test.truststore.type=PKCS12");
propertyValues.add("spring.ssl.bundle.pem.test.truststore.certificate=" + location + "rsa-cert.pem");
propertyValues.add("spring.ssl.bundle.pem.test.truststore.private-key=" + location + "rsa-key.pem");
return propertyValues;
}
@Test
void whenReactiveWebApplicationBeansAreNotConfigured() {
new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations)
.run((context) -> assertThat(context).doesNotHaveBean(ClientHttpRequestFactoryBuilder.class)
.doesNotHaveBean(ClientHttpRequestFactorySettings.class));
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2012-2024 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.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory;
import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.JettyClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ReactorClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpClientProperties}.
*
* @author Phillip Webb
*/
class HttpClientPropertiesTests {
@Nested
class FactoryTests {
@Test
void httpComponentsBuilder() {
assertThat(Factory.HTTP_COMPONENTS.builder())
.isInstanceOf(HttpComponentsClientHttpRequestFactoryBuilder.class);
}
@Test
void jettyBuilder() {
assertThat(Factory.JETTY.builder()).isInstanceOf(JettyClientHttpRequestFactoryBuilder.class);
}
@Test
void reactorBuilder() {
assertThat(Factory.REACTOR.builder()).isInstanceOf(ReactorClientHttpRequestFactoryBuilder.class);
}
@Test
void jdkBuilder() {
assertThat(Factory.JDK.builder()).isInstanceOf(JdkClientHttpRequestFactoryBuilder.class);
}
@Test
void simpleBuilder() {
assertThat(Factory.SIMPLE.builder()).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -23,12 +23,14 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.test.util.ReflectionTestUtils;
@ -49,7 +51,7 @@ import static org.mockito.Mockito.mock;
class RestClientAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, HttpClientAutoConfiguration.class));
@Test
void shouldSupplyBeans() {
@ -156,6 +158,19 @@ class RestClientAutoConfigurationTests {
});
}
@Test
void whenHasFactoryProperty() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withUserConfiguration(RestClientConfig.class)
.withPropertyValues("spring.http.client.factory=simple")
.run((context) -> {
assertThat(context).hasSingleBean(RestClient.class);
RestClient restClient = context.getBean(RestClient.class);
assertThat(restClient).extracting("clientRequestFactory")
.isInstanceOf(SimpleClientHttpRequestFactory.class);
});
}
@Configuration(proxyBeanMethods = false)
static class CodecConfiguration {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -25,6 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@ -36,6 +37,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
@ -56,8 +58,8 @@ import static org.mockito.Mockito.mock;
*/
class RestTemplateAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(RestTemplateAutoConfiguration.class, HttpClientAutoConfiguration.class));
@Test
void restTemplateBuilderConfigurerShouldBeLazilyDefined() {
@ -191,6 +193,18 @@ class RestTemplateAutoConfigurationTests {
.doesNotHaveBean(RestTemplateBuilderConfigurer.class));
}
@Test
void whenHasFactoryProperty() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withUserConfiguration(RestTemplateConfig.class)
.withPropertyValues("spring.http.client.factory=simple")
.run((context) -> {
assertThat(context).hasSingleBean(RestTemplate.class);
RestTemplate restTemplate = context.getBean(RestTemplate.class);
assertThat(restTemplate.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class);
});
}
@Configuration(proxyBeanMethods = false)
static class RestTemplateConfig {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -21,6 +21,8 @@ import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
@ -28,6 +30,7 @@ import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.boot.webservices.client.WebServiceTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@ -45,8 +48,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class WebServiceTemplateAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(WebServiceTemplateAutoConfiguration.class));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(WebServiceTemplateAutoConfiguration.class, HttpClientAutoConfiguration.class));
@Test
void autoConfiguredBuilderShouldNotHaveMarshallerAndUnmarshaller() {
@ -93,6 +96,19 @@ class WebServiceTemplateAutoConfigurationTests {
.run((context) -> assertThat(context).hasNotFailed());
}
@Test
void whenHasFactoryProperty() {
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withPropertyValues("spring.http.client.factory=simple")
.run(assertWebServiceTemplateBuilder((builder) -> {
WebServiceTemplate webServiceTemplate = builder.build();
assertThat(webServiceTemplate.getMessageSenders()).hasSize(1);
ClientHttpRequestMessageSender messageSender = (ClientHttpRequestMessageSender) webServiceTemplate
.getMessageSenders()[0];
assertThat(messageSender.getRequestFactory()).isInstanceOf(SimpleClientHttpRequestFactory.class);
}));
}
private ContextConsumer<AssertableApplicationContext> assertWebServiceTemplateBuilder(
Consumer<WebServiceTemplateBuilder> builder) {
return (context) -> {

View File

@ -36,7 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "management.server.port:0" })
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = { "management.server.port:0", "spring.http.client.factory=simple" })
class SampleActuatorUiApplicationPortTests {
@LocalServerPort

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -39,7 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "server.error.include-message=always" })
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = { "server.error.include-message=always", "spring.http.client.factory=simple" })
class SampleActuatorUiApplicationTests {
@Autowired
@ -50,7 +51,7 @@ class SampleActuatorUiApplicationTests {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
.exchange("/", HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("<title>Hello");
}
@ -74,7 +75,7 @@ class SampleActuatorUiApplicationTests {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
.exchange("/error", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
.exchange("/error", HttpMethod.GET, new HttpEntity<>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(entity.getBody()).contains("<html>")
.contains("<body>")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -48,7 +48,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Madhura Bhave
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = "server.servlet.session.timeout:2")
properties = { "server.servlet.session.timeout:2", "spring.http.client.factory=simple" })
class SampleSessionJdbcApplicationTests {
@Autowired

View File

@ -43,7 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.http.client.factory=simple")
class SampleMethodSecurityApplicationTests {
@LocalServerPort