Skip to content

Commit bd980af

Browse files
committed
Migrates to use new boot http client autoconfig.
Fixes gh-3571
1 parent 5512404 commit bd980af

File tree

4 files changed

+89
-59
lines changed

4 files changed

+89
-59
lines changed

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java

+57-38
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@
1616

1717
package org.springframework.cloud.gateway.server.mvc;
1818

19+
import java.util.Map;
20+
1921
import org.springframework.beans.factory.ObjectProvider;
22+
import org.springframework.boot.SpringApplication;
2023
import org.springframework.boot.autoconfigure.AutoConfiguration;
2124
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2225
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2326
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
24-
import org.springframework.boot.autoconfigure.http.client.HttpClientProperties;
27+
import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory;
2528
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
2629
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
27-
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
28-
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
29-
import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder;
30-
import org.springframework.boot.ssl.SslBundle;
31-
import org.springframework.boot.ssl.SslBundles;
30+
import org.springframework.boot.env.EnvironmentPostProcessor;
31+
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
3232
import org.springframework.boot.web.client.RestClientCustomizer;
3333
import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor;
3434
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcAotRuntimeHintsRegistrar;
@@ -55,8 +55,11 @@
5555
import org.springframework.context.annotation.Bean;
5656
import org.springframework.context.annotation.Import;
5757
import org.springframework.context.annotation.ImportRuntimeHints;
58+
import org.springframework.core.env.ConfigurableEnvironment;
5859
import org.springframework.core.env.Environment;
60+
import org.springframework.core.env.MapPropertySource;
5961
import org.springframework.http.client.ClientHttpRequestFactory;
62+
import org.springframework.util.ClassUtils;
6063
import org.springframework.util.StringUtils;
6164
import org.springframework.web.client.RestClient;
6265

@@ -85,8 +88,12 @@ public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env)
8588
}
8689

8790
@Bean
88-
public RestClientCustomizer gatewayRestClientCustomizer(ClientHttpRequestFactory requestFactory) {
89-
return restClientBuilder -> restClientBuilder.requestFactory(requestFactory);
91+
public RestClientCustomizer gatewayRestClientCustomizer(
92+
ObjectProvider<ClientHttpRequestFactory> requestFactoryProvider) {
93+
return restClientBuilder -> {
94+
// for backwards compatibility if user overrode
95+
requestFactoryProvider.ifAvailable(restClientBuilder::requestFactory);
96+
};
9097
}
9198

9299
@Bean
@@ -111,36 +118,6 @@ public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter() {
111118
return new ForwardedRequestHeadersFilter();
112119
}
113120

114-
@Bean
115-
@ConditionalOnMissingBean
116-
public ClientHttpRequestFactory gatewayClientHttpRequestFactory(HttpClientProperties properties,
117-
SslBundles sslBundles) {
118-
119-
SslBundle sslBundle = null;
120-
if (StringUtils.hasText(properties.getSsl().getBundle())) {
121-
sslBundle = sslBundles.getBundle(properties.getSsl().getBundle());
122-
}
123-
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.ofSslBundle(sslBundle)
124-
.withConnectTimeout(properties.getConnectTimeout())
125-
.withReadTimeout(properties.getReadTimeout())
126-
.withRedirects(ClientHttpRequestFactorySettings.Redirects.DONT_FOLLOW);
127-
128-
ClientHttpRequestFactoryBuilder<?> builder = ClientHttpRequestFactoryBuilder.detect();
129-
if (builder instanceof JdkClientHttpRequestFactoryBuilder) {
130-
// TODO: customize restricted headers
131-
String restrictedHeaders = System.getProperty("jdk.httpclient.allowRestrictedHeaders");
132-
if (!StringUtils.hasText(restrictedHeaders)) {
133-
System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host");
134-
}
135-
else if (StringUtils.hasText(restrictedHeaders) && !restrictedHeaders.contains("host")) {
136-
System.setProperty("jdk.httpclient.allowRestrictedHeaders", restrictedHeaders + ",host");
137-
}
138-
}
139-
140-
// Autodetect
141-
return builder.build(settings);
142-
}
143-
144121
@Bean
145122
@ConditionalOnMissingBean
146123
public GatewayMvcProperties gatewayMvcProperties() {
@@ -222,4 +199,46 @@ public XForwardedRequestHeadersFilterProperties xForwardedRequestHeadersFilterPr
222199
return new XForwardedRequestHeadersFilterProperties();
223200
}
224201

202+
static class GatewayHttpClientEnvironmentPostProcessor implements EnvironmentPostProcessor {
203+
204+
static final boolean APACHE = ClassUtils.isPresent("org.apache.hc.client5.http.impl.classic.HttpClients", null);
205+
static final boolean JETTY = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", null);
206+
static final boolean REACTOR_NETTY = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", null);
207+
static final boolean JDK = ClassUtils.isPresent("java.net.http.HttpClient", null);
208+
static final boolean HIGHER_PRIORITY = APACHE || JETTY || REACTOR_NETTY;
209+
210+
@Override
211+
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
212+
Redirects redirects = environment.getProperty("spring.http.client.redirects", Redirects.class);
213+
if (redirects == null) {
214+
// the user hasn't set anything, change the default
215+
environment.getPropertySources()
216+
.addFirst(new MapPropertySource("gatewayHttpClientProperties",
217+
Map.of("spring.http.client.redirects", Redirects.DONT_FOLLOW)));
218+
}
219+
Factory factory = environment.getProperty("spring.http.client.factory", Factory.class);
220+
boolean setJdkHttpClientProperties = false;
221+
222+
if (factory == null && !HIGHER_PRIORITY) {
223+
// autodetect
224+
setJdkHttpClientProperties = JDK;
225+
}
226+
else if (factory == Factory.JDK) {
227+
setJdkHttpClientProperties = JDK;
228+
}
229+
230+
if (setJdkHttpClientProperties) {
231+
// TODO: customize restricted headers
232+
String restrictedHeaders = System.getProperty("jdk.httpclient.allowRestrictedHeaders");
233+
if (!StringUtils.hasText(restrictedHeaders)) {
234+
System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host");
235+
}
236+
else if (StringUtils.hasText(restrictedHeaders) && !restrictedHeaders.contains("host")) {
237+
System.setProperty("jdk.httpclient.allowRestrictedHeaders", restrictedHeaders + ",host");
238+
}
239+
}
240+
}
241+
242+
}
243+
225244
}

spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories

+3
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier=\
1212
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
1313
org.springframework.cloud.gateway.server.mvc.predicate.MvcPredicateSupplier,\
1414
org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.PredicateSupplier
15+
16+
org.springframework.boot.env.EnvironmentPostProcessor=\
17+
org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration.GatewayHttpClientEnvironmentPostProcessor

spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java

+23-10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
3030
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
3131
import org.springframework.boot.builder.SpringApplicationBuilder;
32+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
33+
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
34+
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
3235
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3336
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
3437
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
@@ -40,8 +43,6 @@
4043
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
4144
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
4245
import org.springframework.context.ConfigurableApplicationContext;
43-
import org.springframework.http.client.JdkClientHttpRequestFactory;
44-
import org.springframework.test.util.ReflectionTestUtils;
4546

4647
import static org.assertj.core.api.Assertions.assertThat;
4748

@@ -135,22 +136,23 @@ void filterEnabledPropertiesWork() {
135136
void gatewayHttpClientPropertiesWork() {
136137
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
137138
.properties("spring.main.web-application-type=none",
138-
"spring.cloud.gateway.mvc.http-client.connect-timeout=1s",
139139
"spring.cloud.gateway.mvc.http-client.connect-timeout=1s",
140140
"spring.cloud.gateway.mvc.http-client.read-timeout=2s",
141141
"spring.cloud.gateway.mvc.http-client.ssl-bundle=mybundle",
142142
"spring.cloud.gateway.mvc.http-client.type=autodetect",
143143
"spring.ssl.bundle.pem.mybundle.keystore.certificate=" + cert,
144144
"spring.ssl.bundle.pem.mybundle.keystore.key=" + key)
145145
.run();
146-
JdkClientHttpRequestFactory requestFactory = context.getBean(JdkClientHttpRequestFactory.class);
146+
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
147147
HttpClientProperties properties = context.getBean(HttpClientProperties.class);
148148
assertThat(properties.getConnectTimeout()).hasSeconds(1);
149149
assertThat(properties.getReadTimeout()).hasSeconds(2);
150150
assertThat(properties.getSsl().getBundle()).isEqualTo("mybundle");
151151
assertThat(properties.getFactory()).isNull();
152-
Object readTimeout = ReflectionTestUtils.getField(requestFactory, "readTimeout");
153-
assertThat(readTimeout).isEqualTo(Duration.ofSeconds(2));
152+
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(2));
153+
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(1));
154+
assertThat(settings.sslBundle()).isNotNull();
155+
assertThat(settings.redirects()).isEqualTo(ClientHttpRequestFactorySettings.Redirects.DONT_FOLLOW);
154156
}
155157

156158
@Test
@@ -164,19 +166,30 @@ void bootHttpClientPropertiesWork() {
164166
"spring.ssl.bundle.pem.mybundle.keystore.certificate=" + cert,
165167
"spring.ssl.bundle.pem.mybundle.keystore.key=" + key)
166168
.run(context -> {
167-
assertThat(context).hasSingleBean(JdkClientHttpRequestFactory.class)
169+
assertThat(context).hasSingleBean(ClientHttpRequestFactorySettings.class)
168170
.hasSingleBean(HttpClientProperties.class);
169171
HttpClientProperties httpClient = context.getBean(HttpClientProperties.class);
170172
assertThat(httpClient.getConnectTimeout()).hasSeconds(1);
171173
assertThat(httpClient.getReadTimeout()).hasSeconds(2);
172174
assertThat(httpClient.getSsl().getBundle()).isEqualTo("mybundle");
173175
assertThat(httpClient.getFactory()).isNull();
174-
JdkClientHttpRequestFactory requestFactory = context.getBean(JdkClientHttpRequestFactory.class);
175-
Object readTimeout = ReflectionTestUtils.getField(requestFactory, "readTimeout");
176-
assertThat(readTimeout).isEqualTo(Duration.ofSeconds(2));
176+
ClientHttpRequestFactorySettings settings = context.getBean(ClientHttpRequestFactorySettings.class);
177+
assertThat(settings.readTimeout()).isEqualTo(Duration.ofSeconds(2));
178+
assertThat(settings.connectTimeout()).isEqualTo(Duration.ofSeconds(1));
179+
assertThat(settings.sslBundle()).isNotNull();
180+
// cant test redirects because EnvironmentPostProcessor is not run
177181
});
178182
}
179183

184+
@Test
185+
void settingHttpClientFactoryWorks() {
186+
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
187+
.properties("spring.main.web-application-type=none", "spring.http.client.factory=simple")
188+
.run();
189+
ClientHttpRequestFactoryBuilder<?> builder = context.getBean(ClientHttpRequestFactoryBuilder.class);
190+
assertThat(builder).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class);
191+
}
192+
180193
@SpringBootConfiguration
181194
@EnableAutoConfiguration
182195
static class TestConfig {

spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestAutoConfiguration.java

+6-11
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,22 @@
2323
import org.springframework.boot.web.client.RestTemplateCustomizer;
2424
import org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration;
2525
import org.springframework.cloud.gateway.server.mvc.test.client.DefaultTestRestClient;
26-
import org.springframework.context.ApplicationContext;
2726
import org.springframework.context.annotation.Bean;
2827
import org.springframework.context.annotation.Lazy;
2928
import org.springframework.core.env.Environment;
3029
import org.springframework.http.HttpHeaders;
3130
import org.springframework.http.MediaType;
32-
import org.springframework.http.client.ClientHttpRequestFactory;
3331

3432
@AutoConfiguration(after = GatewayServerMvcAutoConfiguration.class)
3533
public class TestAutoConfiguration {
3634

3735
@Bean
38-
RestTemplateCustomizer testRestClientRestTemplateCustomizer(ApplicationContext context) {
39-
return restTemplate -> {
40-
restTemplate.setRequestFactory(context.getBean(ClientHttpRequestFactory.class));
41-
restTemplate.setClientHttpRequestInitializers(List.of(request -> {
42-
if (!request.getHeaders().containsKey(HttpHeaders.ACCEPT)) {
43-
request.getHeaders().setAccept(List.of(MediaType.ALL));
44-
}
45-
}));
46-
};
36+
RestTemplateCustomizer testRestClientRestTemplateCustomizer() {
37+
return restTemplate -> restTemplate.setClientHttpRequestInitializers(List.of(request -> {
38+
if (!request.getHeaders().containsKey(HttpHeaders.ACCEPT)) {
39+
request.getHeaders().setAccept(List.of(MediaType.ALL));
40+
}
41+
}));
4742
}
4843

4944
@Bean

0 commit comments

Comments
 (0)