Skip to content

Commit 6efae03

Browse files
authored
Add HttpClientBuilderCustomizer interface (#890)
In addition to leveraging existing Spring Cloud settings, provide the ability for a user to customize settings.
1 parent 36090b9 commit 6efae03

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

docs/modules/ROOT/pages/spring-cloud-openfeign.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ The OkHttpClient, Apache HttpClient 5 and Http2Client Feign clients can be used
139139
You can customize the HTTP client used by providing a bean of either `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
140140

141141
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5, the ones prefixed with `httpclient.okhttp` to OkHttpClient and the ones prefixed with `httpclient.http2` to Http2Client. You can find a full list of properties you can customise in the appendix.
142+
If you can not configure Apache HttpClient 5 by using properties, there is an `HttpClientBuilderCustomizer` interface for programmatic configuration.
142143

143144
TIP: Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.
144145

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClient5FeignConfiguration.java

+37-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.security.NoSuchAlgorithmException;
2121
import java.security.SecureRandom;
2222
import java.security.cert.X509Certificate;
23+
import java.util.List;
2324
import java.util.concurrent.TimeUnit;
2425

2526
import javax.net.ssl.SSLContext;
@@ -31,6 +32,7 @@
3132
import org.apache.commons.logging.LogFactory;
3233
import org.apache.hc.client5.http.config.RequestConfig;
3334
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
35+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
3436
import org.apache.hc.client5.http.impl.classic.HttpClients;
3537
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
3638
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
@@ -46,6 +48,7 @@
4648
import org.apache.hc.core5.util.TimeValue;
4749
import org.apache.hc.core5.util.Timeout;
4850

51+
import org.springframework.beans.factory.ObjectProvider;
4952
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5053
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
5154
import org.springframework.context.annotation.Bean;
@@ -56,6 +59,7 @@
5659
*
5760
* @author Nguyen Ky Thanh
5861
* @author changjin wei(魏昌进)
62+
* @author Kwangyong Kim
5963
*/
6064
@Configuration(proxyBeanMethods = false)
6165
@ConditionalOnMissingBean(CloseableHttpClient.class)
@@ -85,18 +89,23 @@ public HttpClientConnectionManager hc5ConnectionManager(FeignHttpClientPropertie
8589

8690
@Bean
8791
public CloseableHttpClient httpClient5(HttpClientConnectionManager connectionManager,
88-
FeignHttpClientProperties httpClientProperties) {
89-
httpClient5 = HttpClients.custom().disableCookieManagement().useSystemProperties()
90-
.setConnectionManager(connectionManager).evictExpiredConnections()
91-
.setDefaultRequestConfig(RequestConfig.custom()
92-
.setConnectTimeout(
93-
Timeout.of(httpClientProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS))
94-
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
95-
.setConnectionRequestTimeout(
96-
Timeout.of(httpClientProperties.getHc5().getConnectionRequestTimeout(),
97-
httpClientProperties.getHc5().getConnectionRequestTimeoutUnit()))
98-
.build())
99-
.build();
92+
FeignHttpClientProperties httpClientProperties,
93+
ObjectProvider<List<HttpClientBuilderCustomizer>> customizerProvider) {
94+
HttpClientBuilder httpClientBuilder = HttpClients.custom().disableCookieManagement().useSystemProperties()
95+
.setConnectionManager(connectionManager).evictExpiredConnections()
96+
.setDefaultRequestConfig(RequestConfig.custom()
97+
.setConnectTimeout(
98+
Timeout.of(httpClientProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS))
99+
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
100+
.setConnectionRequestTimeout(
101+
Timeout.of(httpClientProperties.getHc5().getConnectionRequestTimeout(),
102+
httpClientProperties.getHc5().getConnectionRequestTimeoutUnit()))
103+
.build());
104+
105+
customizerProvider.getIfAvailable(List::of)
106+
.forEach(c -> c.customize(httpClientBuilder));
107+
108+
httpClient5 = httpClientBuilder.build();
100109
return httpClient5;
101110
}
102111

@@ -146,4 +155,19 @@ public X509Certificate[] getAcceptedIssuers() {
146155

147156
}
148157

158+
/**
159+
* Callback interface that customize {@link HttpClientBuilder} objects before HttpClient created.
160+
*
161+
* @author Kwangyong Kim
162+
* @since 4.1.0
163+
*/
164+
public interface HttpClientBuilderCustomizer {
165+
166+
/**
167+
* Customize HttpClientBuilder.
168+
* @param builder the {@code HttpClientBuilder} to customize
169+
*/
170+
void customize(HttpClientBuilder builder);
171+
172+
}
149173
}

spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignHttpClient5ConfigurationTests.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,21 +19,29 @@
1919
import feign.Client;
2020
import feign.hc5.ApacheHttp5Client;
2121
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
22+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
2223
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
2324
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
2425
import org.junit.jupiter.api.Test;
26+
import org.mockito.Mockito;
2527

2628
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2729
import org.springframework.boot.WebApplicationType;
2830
import org.springframework.boot.builder.SpringApplicationBuilder;
31+
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.HttpClientBuilderCustomizer;
2932
import org.springframework.context.ConfigurableApplicationContext;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
3035

3136
import static org.assertj.core.api.Assertions.assertThat;
3237
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
38+
import static org.mockito.ArgumentMatchers.any;
39+
import static org.mockito.Mockito.verify;
3340

3441
/**
3542
* @author Nguyen Ky Thanh
3643
* @author Olga Maciaszek-Sharma
44+
* @author Kwangyong Kim
3745
*/
3846
class FeignHttpClient5ConfigurationTests {
3947

@@ -72,4 +80,28 @@ void shouldNotInstantiateHttpClient5ByWhenDependenciesPresentButPropertyDisabled
7280
}
7381
}
7482

83+
@Test
84+
void shouldInstantiateHttpClient5ByUsingHttpClientBuilderCustomizer() {
85+
ConfigurableApplicationContext context = new SpringApplicationBuilder()
86+
.web(WebApplicationType.NONE)
87+
.sources(FeignAutoConfiguration.class, Config.class)
88+
.run();
89+
90+
CloseableHttpClient httpClient = context.getBean(CloseableHttpClient.class);
91+
assertThat(httpClient).isNotNull();
92+
HttpClientBuilderCustomizer customizer = context.getBean(HttpClientBuilderCustomizer.class);
93+
verify(customizer).customize(any(HttpClientBuilder.class));
94+
95+
if (context != null) {
96+
context.close();
97+
}
98+
}
99+
100+
@Configuration
101+
static class Config {
102+
@Bean
103+
HttpClientBuilderCustomizer customizer() {
104+
return Mockito.mock(HttpClientBuilderCustomizer.class);
105+
}
106+
}
75107
}

0 commit comments

Comments
 (0)