Skip to content

Commit

Permalink
Add HttpClientBuilderCustomizer interface (#890)
Browse files Browse the repository at this point in the history
In addition to leveraging existing Spring Cloud settings, provide the ability for a user to customize settings.
  • Loading branch information
bananayong authored Nov 3, 2023
1 parent 36090b9 commit 6efae03
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ The OkHttpClient, Apache HttpClient 5 and Http2Client Feign clients can be used
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.

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.
If you can not configure Apache HttpClient 5 by using properties, there is an `HttpClientBuilderCustomizer` interface for programmatic configuration.

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2023 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.
Expand All @@ -20,6 +20,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
Expand All @@ -31,6 +32,7 @@
import org.apache.commons.logging.LogFactory;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
Expand All @@ -46,6 +48,7 @@
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
Expand All @@ -56,6 +59,7 @@
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @author Kwangyong Kim
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
Expand Down Expand Up @@ -85,18 +89,23 @@ public HttpClientConnectionManager hc5ConnectionManager(FeignHttpClientPropertie

@Bean
public CloseableHttpClient httpClient5(HttpClientConnectionManager connectionManager,
FeignHttpClientProperties httpClientProperties) {
httpClient5 = HttpClients.custom().disableCookieManagement().useSystemProperties()
.setConnectionManager(connectionManager).evictExpiredConnections()
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(
Timeout.of(httpClientProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS))
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.setConnectionRequestTimeout(
Timeout.of(httpClientProperties.getHc5().getConnectionRequestTimeout(),
httpClientProperties.getHc5().getConnectionRequestTimeoutUnit()))
.build())
.build();
FeignHttpClientProperties httpClientProperties,
ObjectProvider<List<HttpClientBuilderCustomizer>> customizerProvider) {
HttpClientBuilder httpClientBuilder = HttpClients.custom().disableCookieManagement().useSystemProperties()
.setConnectionManager(connectionManager).evictExpiredConnections()
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(
Timeout.of(httpClientProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS))
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.setConnectionRequestTimeout(
Timeout.of(httpClientProperties.getHc5().getConnectionRequestTimeout(),
httpClientProperties.getHc5().getConnectionRequestTimeoutUnit()))
.build());

customizerProvider.getIfAvailable(List::of)
.forEach(c -> c.customize(httpClientBuilder));

httpClient5 = httpClientBuilder.build();
return httpClient5;
}

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

}

/**
* Callback interface that customize {@link HttpClientBuilder} objects before HttpClient created.
*
* @author Kwangyong Kim
* @since 4.1.0
*/
public interface HttpClientBuilderCustomizer {

/**
* Customize HttpClientBuilder.
* @param builder the {@code HttpClientBuilder} to customize
*/
void customize(HttpClientBuilder builder);

}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2023 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.
Expand All @@ -19,21 +19,29 @@
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.HttpClientBuilderCustomizer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;

/**
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
* @author Kwangyong Kim
*/
class FeignHttpClient5ConfigurationTests {

Expand Down Expand Up @@ -72,4 +80,28 @@ void shouldNotInstantiateHttpClient5ByWhenDependenciesPresentButPropertyDisabled
}
}

@Test
void shouldInstantiateHttpClient5ByUsingHttpClientBuilderCustomizer() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.web(WebApplicationType.NONE)
.sources(FeignAutoConfiguration.class, Config.class)
.run();

CloseableHttpClient httpClient = context.getBean(CloseableHttpClient.class);
assertThat(httpClient).isNotNull();
HttpClientBuilderCustomizer customizer = context.getBean(HttpClientBuilderCustomizer.class);
verify(customizer).customize(any(HttpClientBuilder.class));

if (context != null) {
context.close();
}
}

@Configuration
static class Config {
@Bean
HttpClientBuilderCustomizer customizer() {
return Mockito.mock(HttpClientBuilderCustomizer.class);
}
}
}

0 comments on commit 6efae03

Please sign in to comment.