Skip to content

Commit d307d57

Browse files
committed
Apply HTTP Service Client properties and use fallback beans
Update service client configuration so that properties are always applied when present. Any settings and factory/connector beans that are present are now only used as fallbacks. Fixes gh-46915
1 parent 67dddcc commit d307d57

File tree

8 files changed

+176
-54
lines changed

8 files changed

+176
-54
lines changed

module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/ClientHttpRequestFactories.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,64 @@ public final class ClientHttpRequestFactories {
4646

4747
private final @Nullable AbstractHttpRequestFactoryProperties[] orderedProperties;
4848

49+
private final @Nullable ClientHttpRequestFactoryBuilder<?> fallbackBuilder;
50+
51+
private final @Nullable ClientHttpRequestFactorySettings fallbackSettings;
52+
4953
public ClientHttpRequestFactories(ObjectFactory<SslBundles> sslBundles,
5054
@Nullable AbstractHttpRequestFactoryProperties... orderedProperties) {
55+
this(null, null, sslBundles, orderedProperties);
56+
}
57+
58+
public ClientHttpRequestFactories(@Nullable ClientHttpRequestFactoryBuilder<?> fallbackBuilder,
59+
@Nullable ClientHttpRequestFactorySettings fallbackSettings, ObjectFactory<SslBundles> sslBundles,
60+
@Nullable AbstractHttpRequestFactoryProperties... orderedProperties) {
61+
this.fallbackBuilder = fallbackBuilder;
62+
this.fallbackSettings = fallbackSettings;
5163
this.sslBundles = sslBundles;
5264
this.orderedProperties = orderedProperties;
5365
}
5466

5567
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
5668
public ClientHttpRequestFactoryBuilder<?> builder(@Nullable ClassLoader classLoader) {
57-
Factory factory = getProperty(AbstractHttpRequestFactoryProperties::getFactory);
58-
return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect(classLoader);
69+
Factory factory = getProperty(AbstractHttpRequestFactoryProperties::getFactory, Objects::nonNull, null,
70+
Function.identity());
71+
if (factory != null) {
72+
return factory.builder();
73+
}
74+
return (this.fallbackBuilder != null) ? this.fallbackBuilder
75+
: ClientHttpRequestFactoryBuilder.detect(classLoader);
5976
}
6077

6178
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
6279
public ClientHttpRequestFactorySettings settings() {
63-
HttpRedirects redirects = getProperty(AbstractHttpRequestFactoryProperties::getRedirects);
64-
Duration connectTimeout = getProperty(AbstractHttpRequestFactoryProperties::getConnectTimeout);
65-
Duration readTimeout = getProperty(AbstractHttpRequestFactoryProperties::getReadTimeout);
80+
HttpRedirects redirects = getProperty(AbstractHttpRequestFactoryProperties::getRedirects, Objects::nonNull,
81+
this.fallbackSettings, ClientHttpRequestFactorySettings::redirects);
82+
Duration connectTimeout = getProperty(AbstractHttpRequestFactoryProperties::getConnectTimeout, Objects::nonNull,
83+
this.fallbackSettings, ClientHttpRequestFactorySettings::connectTimeout);
84+
Duration readTimeout = getProperty(AbstractHttpRequestFactoryProperties::getReadTimeout, Objects::nonNull,
85+
this.fallbackSettings, ClientHttpRequestFactorySettings::readTimeout);
6686
String sslBundleName = getProperty(AbstractHttpRequestFactoryProperties::getSsl, Ssl::getBundle,
67-
StringUtils::hasLength);
87+
StringUtils::hasLength, null, Function.identity());
6888
SslBundle sslBundle = (StringUtils.hasLength(sslBundleName))
69-
? this.sslBundles.getObject().getBundle(sslBundleName) : null;
89+
? this.sslBundles.getObject().getBundle(sslBundleName) : fallbackSslBundle();
7090
return new ClientHttpRequestFactorySettings(redirects, connectTimeout, readTimeout, sslBundle);
7191
}
7292

93+
private @Nullable SslBundle fallbackSslBundle() {
94+
return (this.fallbackSettings != null) ? this.fallbackSettings.sslBundle() : null;
95+
}
96+
7397
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
74-
private <T> @Nullable T getProperty(Function<AbstractHttpRequestFactoryProperties, @Nullable T> accessor) {
75-
return getProperty(accessor, Function.identity(), Objects::nonNull);
98+
private <T, F> @Nullable T getProperty(Function<AbstractHttpRequestFactoryProperties, @Nullable T> accessor,
99+
Predicate<@Nullable T> predicate, @Nullable F fallback, Function<F, @Nullable T> fallbackAccessor) {
100+
return getProperty(accessor, Function.identity(), predicate, fallback, fallbackAccessor);
76101
}
77102

78-
private <P, T> @Nullable T getProperty(Function<AbstractHttpRequestFactoryProperties, @Nullable P> accessor,
79-
Function<P, T> extractor, Predicate<@Nullable T> predicate) {
103+
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
104+
private <P, T, F> @Nullable T getProperty(Function<AbstractHttpRequestFactoryProperties, @Nullable P> accessor,
105+
Function<@Nullable P, @Nullable T> extractor, Predicate<@Nullable T> predicate, @Nullable F fallback,
106+
Function<F, @Nullable T> fallbackAccessor) {
80107
for (AbstractHttpRequestFactoryProperties properties : this.orderedProperties) {
81108
if (properties != null) {
82109
P value = accessor.apply(properties);
@@ -86,7 +113,7 @@ public ClientHttpRequestFactorySettings settings() {
86113
}
87114
}
88115
}
89-
return null;
116+
return (fallback != null) ? fallbackAccessor.apply(fallback) : null;
90117
}
91118

92119
}

module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/reactive/ClientHttpConnectors.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,63 @@ public final class ClientHttpConnectors {
4646

4747
private final @Nullable AbstractClientHttpConnectorProperties[] orderedProperties;
4848

49+
private final @Nullable ClientHttpConnectorBuilder<?> fallbackBuilder;
50+
51+
private final @Nullable ClientHttpConnectorSettings fallbackSettings;
52+
4953
public ClientHttpConnectors(ObjectFactory<SslBundles> sslBundles,
5054
@Nullable AbstractClientHttpConnectorProperties... orderedProperties) {
55+
this(null, null, sslBundles, orderedProperties);
56+
}
57+
58+
public ClientHttpConnectors(@Nullable ClientHttpConnectorBuilder<?> fallbackBuilder,
59+
@Nullable ClientHttpConnectorSettings fallbackSettings, ObjectFactory<SslBundles> sslBundles,
60+
@Nullable AbstractClientHttpConnectorProperties... orderedProperties) {
61+
this.fallbackBuilder = fallbackBuilder;
62+
this.fallbackSettings = fallbackSettings;
5163
this.sslBundles = sslBundles;
5264
this.orderedProperties = orderedProperties;
5365
}
5466

5567
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
5668
public ClientHttpConnectorBuilder<?> builder(@Nullable ClassLoader classLoader) {
57-
Connector connector = getProperty(AbstractClientHttpConnectorProperties::getConnector);
58-
return (connector != null) ? connector.builder() : ClientHttpConnectorBuilder.detect(classLoader);
69+
Connector connector = getProperty(AbstractClientHttpConnectorProperties::getConnector, Objects::nonNull, null,
70+
Function.identity());
71+
if (connector != null) {
72+
return connector.builder();
73+
}
74+
return (this.fallbackBuilder != null) ? this.fallbackBuilder : ClientHttpConnectorBuilder.detect(classLoader);
5975
}
6076

6177
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
6278
public ClientHttpConnectorSettings settings() {
63-
HttpRedirects redirects = getProperty(AbstractClientHttpConnectorProperties::getRedirects);
64-
Duration connectTimeout = getProperty(AbstractClientHttpConnectorProperties::getConnectTimeout);
65-
Duration readTimeout = getProperty(AbstractClientHttpConnectorProperties::getReadTimeout);
79+
HttpRedirects redirects = getProperty(AbstractClientHttpConnectorProperties::getRedirects, Objects::nonNull,
80+
this.fallbackSettings, ClientHttpConnectorSettings::redirects);
81+
Duration connectTimeout = getProperty(AbstractClientHttpConnectorProperties::getConnectTimeout,
82+
Objects::nonNull, this.fallbackSettings, ClientHttpConnectorSettings::connectTimeout);
83+
Duration readTimeout = getProperty(AbstractClientHttpConnectorProperties::getReadTimeout, Objects::nonNull,
84+
this.fallbackSettings, ClientHttpConnectorSettings::readTimeout);
6685
String sslBundleName = getProperty(AbstractClientHttpConnectorProperties::getSsl, Ssl::getBundle,
67-
StringUtils::hasText);
86+
StringUtils::hasText, null, Function.identity());
6887
SslBundle sslBundle = (StringUtils.hasLength(sslBundleName))
69-
? this.sslBundles.getObject().getBundle(sslBundleName) : null;
88+
? this.sslBundles.getObject().getBundle(sslBundleName) : fallbackSslBundle();
7089
return new ClientHttpConnectorSettings(redirects, connectTimeout, readTimeout, sslBundle);
7190
}
7291

92+
private @Nullable SslBundle fallbackSslBundle() {
93+
return (this.fallbackSettings != null) ? this.fallbackSettings.sslBundle() : null;
94+
}
95+
7396
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
74-
private <T> @Nullable T getProperty(Function<AbstractClientHttpConnectorProperties, @Nullable T> accessor) {
75-
return getProperty(accessor, Function.identity(), Objects::nonNull);
97+
private <T, F> @Nullable T getProperty(Function<AbstractClientHttpConnectorProperties, @Nullable T> accessor,
98+
Predicate<@Nullable T> predicate, @Nullable F fallback, Function<F, @Nullable T> fallbackAccessor) {
99+
return getProperty(accessor, Function.identity(), predicate, fallback, fallbackAccessor);
76100
}
77101

78-
private <P, T> @Nullable T getProperty(Function<AbstractClientHttpConnectorProperties, @Nullable P> accessor,
79-
Function<P, @Nullable T> extractor, Predicate<@Nullable T> predicate) {
102+
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
103+
private <P, T, F> @Nullable T getProperty(Function<AbstractClientHttpConnectorProperties, @Nullable P> accessor,
104+
Function<@Nullable P, @Nullable T> extractor, Predicate<@Nullable T> predicate, @Nullable F fallback,
105+
Function<F, @Nullable T> fallbackAccessor) {
80106
for (AbstractClientHttpConnectorProperties properties : this.orderedProperties) {
81107
if (properties != null) {
82108
P value = accessor.apply(properties);
@@ -86,7 +112,7 @@ public ClientHttpConnectorSettings settings() {
86112
}
87113
}
88114
}
89-
return null;
115+
return (fallback != null) ? fallbackAccessor.apply(fallback) : null;
90116
}
91117

92118
}

module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/RestClientHttpServiceClientConfiguration.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2222
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
2323
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
24-
import org.springframework.boot.http.client.autoconfigure.HttpClientProperties;
2524
import org.springframework.boot.restclient.RestClientCustomizer;
2625
import org.springframework.boot.ssl.SslBundles;
2726
import org.springframework.context.annotation.Bean;
@@ -53,15 +52,13 @@ public void setBeanClassLoader(ClassLoader classLoader) {
5352

5453
@Bean
5554
RestClientPropertiesHttpServiceGroupConfigurer restClientPropertiesHttpServiceGroupConfigurer(
56-
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientProperties> httpClientProperties,
57-
HttpClientServiceProperties serviceProperties,
55+
ObjectProvider<SslBundles> sslBundles, HttpClientServiceProperties serviceProperties,
5856
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> clientFactoryBuilder,
5957
ObjectProvider<ClientHttpRequestFactorySettings> clientHttpRequestFactorySettings,
6058
ObjectProvider<ApiVersionInserter> apiVersionInserter,
6159
ObjectProvider<ApiVersionFormatter> apiVersionFormatter) {
62-
return new RestClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles,
63-
httpClientProperties.getIfAvailable(), serviceProperties, clientFactoryBuilder,
64-
clientHttpRequestFactorySettings, apiVersionInserter, apiVersionFormatter);
60+
return new RestClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles, serviceProperties,
61+
clientFactoryBuilder, clientHttpRequestFactorySettings, apiVersionInserter, apiVersionFormatter);
6562
}
6663

6764
@Bean

module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/RestClientPropertiesHttpServiceGroupConfigurer.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ class RestClientPropertiesHttpServiceGroupConfigurer implements RestClientHttpSe
4646

4747
private final ObjectProvider<SslBundles> sslBundles;
4848

49-
private final @Nullable HttpClientProperties clientProperties;
50-
5149
private final HttpClientServiceProperties serviceProperties;
5250

5351
private final ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder;
@@ -59,14 +57,13 @@ class RestClientPropertiesHttpServiceGroupConfigurer implements RestClientHttpSe
5957
private final @Nullable ApiVersionFormatter apiVersionFormatter;
6058

6159
RestClientPropertiesHttpServiceGroupConfigurer(ClassLoader classLoader, ObjectProvider<SslBundles> sslBundles,
62-
@Nullable HttpClientProperties clientProperties, HttpClientServiceProperties serviceProperties,
60+
HttpClientServiceProperties serviceProperties,
6361
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> requestFactoryBuilder,
6462
ObjectProvider<ClientHttpRequestFactorySettings> requestFactorySettings,
6563
ObjectProvider<ApiVersionInserter> apiVersionInserter,
6664
ObjectProvider<ApiVersionFormatter> apiVersionFormatter) {
6765
this.classLoader = classLoader;
6866
this.sslBundles = sslBundles;
69-
this.clientProperties = clientProperties;
7067
this.serviceProperties = serviceProperties;
7168
this.requestFactoryBuilder = requestFactoryBuilder;
7269
this.requestFactorySettings = requestFactorySettings;
@@ -97,11 +94,11 @@ private PropertiesRestClientCustomizer getPropertiesRestClientCustomizer(
9794
}
9895

9996
private ClientHttpRequestFactory getRequestFactory(HttpClientServiceProperties.@Nullable Group groupProperties) {
100-
ClientHttpRequestFactories factories = new ClientHttpRequestFactories(this.sslBundles, groupProperties,
101-
this.serviceProperties, this.clientProperties);
102-
ClientHttpRequestFactoryBuilder<?> builder = this.requestFactoryBuilder
103-
.getIfAvailable(() -> factories.builder(this.classLoader));
104-
ClientHttpRequestFactorySettings settings = this.requestFactorySettings.getIfAvailable(factories::settings);
97+
ClientHttpRequestFactories factories = new ClientHttpRequestFactories(
98+
this.requestFactoryBuilder.getIfAvailable(), this.requestFactorySettings.getIfAvailable(),
99+
this.sslBundles, groupProperties, this.serviceProperties);
100+
ClientHttpRequestFactoryBuilder<?> builder = factories.builder(this.classLoader);
101+
ClientHttpRequestFactorySettings settings = factories.settings();
105102
return builder.build(settings);
106103
}
107104

module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.assertj.core.extractor.Extractors;
2727
import org.junit.jupiter.api.Test;
28+
import org.mockito.ArgumentCaptor;
2829

2930
import org.springframework.aop.Advisor;
3031
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
@@ -39,16 +40,22 @@
3940
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
4041
import org.springframework.context.annotation.Bean;
4142
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.http.client.ClientHttpRequestFactory;
4244
import org.springframework.test.web.client.MockRestServiceServer;
4345
import org.springframework.web.client.RestClient;
4446
import org.springframework.web.client.RestClient.Builder;
4547
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
4648
import org.springframework.web.service.annotation.GetExchange;
4749
import org.springframework.web.service.registry.HttpServiceGroup;
50+
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback;
51+
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.Groups;
4852
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
4953
import org.springframework.web.service.registry.ImportHttpServices;
5054

5155
import static org.assertj.core.api.Assertions.assertThat;
56+
import static org.mockito.BDDMockito.given;
57+
import static org.mockito.BDDMockito.then;
58+
import static org.mockito.Mockito.mock;
5259
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
5360
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
5461
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@@ -95,6 +102,37 @@ void configuresClientFromProperties() {
95102
});
96103
}
97104

105+
@Test // gh-46915
106+
void configuresClientFromPropertiesWhenHasHttpClientAutoConfiguration() {
107+
this.contextRunner.withConfiguration(AutoConfigurations.of(HttpClientAutoConfiguration.class))
108+
.withPropertyValues("spring.http.client.service.connect-timeout=10s",
109+
"spring.http.client.service.group.one.connect-timeout=5s")
110+
.withUserConfiguration(HttpClientConfiguration.class, MockRestServiceServerConfiguration.class)
111+
.run((context) -> {
112+
RestClientPropertiesHttpServiceGroupConfigurer configurer = context
113+
.getBean(RestClientPropertiesHttpServiceGroupConfigurer.class);
114+
Groups<RestClient.Builder> groups = mock();
115+
configurer.configureGroups(groups);
116+
ArgumentCaptor<ClientCallback<RestClient.Builder>> callbackCaptor = ArgumentCaptor.captor();
117+
then(groups).should().forEachClient(callbackCaptor.capture());
118+
ClientCallback<RestClient.Builder> callback = callbackCaptor.getValue();
119+
assertConnectTimeout(callback, "one", 5000);
120+
assertConnectTimeout(callback, "two", 10000);
121+
});
122+
}
123+
124+
private void assertConnectTimeout(ClientCallback<RestClient.Builder> callback, String name,
125+
long expectedReadTimeout) {
126+
HttpServiceGroup group = mock();
127+
given(group.name()).willReturn(name);
128+
RestClient.Builder builder = mock();
129+
callback.withClient(group, builder);
130+
ArgumentCaptor<ClientHttpRequestFactory> requestFactoryCaptor = ArgumentCaptor.captor();
131+
then(builder).should().requestFactory(requestFactoryCaptor.capture());
132+
ClientHttpRequestFactory client = requestFactoryCaptor.getValue();
133+
assertThat(client).extracting("connectTimeout").isEqualTo(expectedReadTimeout);
134+
}
135+
98136
@Test
99137
void whenHasUserDefinedRequestFactoryBuilder() {
100138
this.contextRunner.withPropertyValues("spring.http.client.service.base-url=https://example.com")

module/spring-boot-webclient/src/main/java/org/springframework/boot/webclient/autoconfigure/service/WebClientHttpServiceClientConfiguration.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.springframework.beans.factory.BeanClassLoaderAware;
2020
import org.springframework.beans.factory.ObjectProvider;
2121
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
22-
import org.springframework.boot.http.client.autoconfigure.reactive.HttpReactiveClientProperties;
2322
import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder;
2423
import org.springframework.boot.http.client.reactive.ClientHttpConnectorSettings;
2524
import org.springframework.boot.ssl.SslBundles;
@@ -53,15 +52,13 @@ public void setBeanClassLoader(ClassLoader classLoader) {
5352

5453
@Bean
5554
WebClientPropertiesHttpServiceGroupConfigurer webClientPropertiesHttpServiceGroupConfigurer(
56-
ObjectProvider<SslBundles> sslBundles, HttpReactiveClientProperties httpReactiveClientProperties,
57-
ReactiveHttpClientServiceProperties serviceProperties,
55+
ObjectProvider<SslBundles> sslBundles, ReactiveHttpClientServiceProperties serviceProperties,
5856
ObjectProvider<ClientHttpConnectorBuilder<?>> clientConnectorBuilder,
5957
ObjectProvider<ClientHttpConnectorSettings> clientConnectorSettings,
6058
ObjectProvider<ApiVersionInserter> apiVersionInserter,
6159
ObjectProvider<ApiVersionFormatter> apiVersionFormatter) {
62-
return new WebClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles,
63-
httpReactiveClientProperties, serviceProperties, clientConnectorBuilder, clientConnectorSettings,
64-
apiVersionInserter, apiVersionFormatter);
60+
return new WebClientPropertiesHttpServiceGroupConfigurer(this.beanClassLoader, sslBundles, serviceProperties,
61+
clientConnectorBuilder, clientConnectorSettings, apiVersionInserter, apiVersionFormatter);
6562
}
6663

6764
@Bean

0 commit comments

Comments
 (0)