Add HttpClientBuilderCustomizer interface (#890)

In addition to leveraging existing Spring Cloud settings, provide the ability for a user to customize settings.
This commit is contained in:
Kwangyong Kim
2023-11-03 20:20:52 +09:00
committed by GitHub
parent 36090b9d09
commit 6efae0380b
3 changed files with 71 additions and 14 deletions

View File

@@ -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.

View File

@@ -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.
@@ -20,6 +20,7 @@ import java.security.KeyManagementException;
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;
@@ -31,6 +32,7 @@ import org.apache.commons.logging.Log;
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;
@@ -46,6 +48,7 @@ import org.apache.hc.core5.ssl.SSLContexts;
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;
@@ -56,6 +59,7 @@ import org.springframework.context.annotation.Configuration;
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @author Kwangyong Kim
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@@ -85,18 +89,23 @@ public class HttpClient5FeignConfiguration {
@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;
}
@@ -146,4 +155,19 @@ public class HttpClient5FeignConfiguration {
}
/**
* 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);
}
}

View File

@@ -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.
@@ -19,21 +19,29 @@ package org.springframework.cloud.openfeign;
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 {
@@ -72,4 +80,28 @@ class FeignHttpClient5ConfigurationTests {
}
}
@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);
}
}
}