Add Apache HC5 client option (#498)

This commit is contained in:
Thanh
2021-03-11 14:14:34 +01:00
committed by GitHub
parent 2a23b6d304
commit e3439f38bc
20 changed files with 875 additions and 23 deletions

View File

@@ -28,6 +28,7 @@ import javax.annotation.PreDestroy;
import com.fasterxml.jackson.databind.Module;
import feign.Client;
import feign.Feign;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
@@ -73,6 +74,7 @@ import org.springframework.data.domain.Sort;
* @author Nikita Konev
* @author Tim Peeters
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@@ -169,6 +171,7 @@ public class FeignAutoConfiguration {
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
@@ -287,6 +290,23 @@ public class FeignAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(org.apache.hc.client5.http.impl.classic.CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@Import(org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.class)
protected static class HttpClient5FeignConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(
org.apache.hc.client5.http.impl.classic.CloseableHttpClient httpClient5) {
return new ApacheHttp5Client(httpClient5);
}
}
static class DefaultFeignTargeterConditions extends AllNestedConditions {
DefaultFeignTargeterConditions() {

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
/**
* @author Nguyen Ky Thanh
*/
public class HttpClient5DisabledConditions extends AnyNestedCondition {
public HttpClient5DisabledConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass("feign.hc5.ApacheHttp5Client")
static class ApacheHttp5ClientClassMissing {
}
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "false",
matchIfMissing = true)
static class HttpClient5Disabled {
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.clientconfig;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
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.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Default configuration for {@link CloseableHttpClient}.
*
* @author Nguyen Ky Thanh
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClient5FeignConfiguration {
private static final Log LOG = LogFactory.getLog(HttpClient5FeignConfiguration.class);
private CloseableHttpClient httpClient5;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager hc5ConnectionManager(
FeignHttpClientProperties httpClientProperties) {
return PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(httpsSSLConnectionSocketFactory(
httpClientProperties.isDisableSslValidation()))
.setMaxConnTotal(httpClientProperties.getMaxConnections())
.setMaxConnPerRoute(httpClientProperties.getMaxConnectionsPerRoute())
.setConnPoolPolicy(PoolReusePolicy.valueOf(
httpClientProperties.getHc5().getPoolReusePolicy().name()))
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.valueOf(
httpClientProperties.getHc5().getPoolConcurrencyPolicy().name()))
.setConnectionTimeToLive(
TimeValue.of(httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit()))
.setDefaultSocketConfig(SocketConfig.custom()
.setSoTimeout(Timeout.of(
httpClientProperties.getHc5().getSocketTimeout(),
httpClientProperties.getHc5().getSocketTimeoutUnit()))
.build())
.build();
}
@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())
.build())
.build();
return httpClient5;
}
@PreDestroy
public void destroy() {
if (httpClient5 != null) {
httpClient5.close(CloseMode.GRACEFUL);
}
}
private LayeredConnectionSocketFactory httpsSSLConnectionSocketFactory(
boolean isDisableSslValidation) {
final SSLConnectionSocketFactoryBuilder sslConnectionSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder
.create().setTlsVersions(TLS.V_1_3, TLS.V_1_2);
if (isDisableSslValidation) {
try {
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null,
new TrustManager[] { new DisabledValidationTrustManager() },
new SecureRandom());
sslConnectionSocketFactoryBuilder.setSslContext(sslContext);
}
catch (NoSuchAlgorithmException e) {
LOG.warn("Error creating SSLContext", e);
}
catch (KeyManagementException e) {
LOG.warn("Error creating SSLContext", e);
}
}
else {
sslConnectionSocketFactoryBuilder
.setSslContext(SSLContexts.createSystemDefault());
}
return sslConnectionSocketFactoryBuilder.build();
}
static class DisabledValidationTrustManager implements X509TrustManager {
DisabledValidationTrustManager() {
}
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Import;
* <code>spring.cloud.loadbalancer.ribbon.enabled</code> to <code>true</code>.
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @since 2.2.0
*/
@ConditionalOnClass(Feign.class)
@@ -51,6 +52,7 @@ import org.springframework.context.annotation.Import;
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.classic.HttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Configuration instantiating a {@link BlockingLoadBalancerClient}-based {@link Client}
* object that uses {@link ApacheHttp5Client} under the hood.
*
* @author Nguyen Ky Thanh
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@Import(HttpClient5FeignConfiguration.class)
class HttpClient5FeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient,
HttpClient httpClient5) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnBean(LoadBalancedRetryFactory.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
havingValue = "true", matchIfMissing = true)
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
HttpClient httpClient5,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
Client delegate = new ApacheHttp5Client(httpClient5);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
loadBalancedRetryFactories.get(0));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.HttpClient5DisabledConditions;
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@@ -40,12 +41,14 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
* object that uses {@link ApacheHttpClient} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancerConfiguration {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -41,6 +41,7 @@ import org.springframework.context.annotation.Primary;
*
* @author Dave Syer
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
*/
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
@@ -53,6 +54,7 @@ import org.springframework.context.annotation.Primary;
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
HttpClient5FeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.ribbon;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.classic.HttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Configuration instantiating a {@link LoadBalancerFeignClient}-based {@link Client}
* object that uses {@link ApacheHttp5Client} under the hood.
*
* @author Nguyen Ky Thanh
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
@Import(HttpClient5FeignConfiguration.class)
class HttpClient5FeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, HttpClient httpClient5) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -24,18 +24,22 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.HttpClient5DisabledConditions;
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Ryan Baxter
* @author Nguyen Ky Thanh
*/
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
@@ -82,6 +83,11 @@ public class FeignHttpClientProperties {
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
/**
* Apache HttpClient5 additional properties.
*/
private Hc5Properties hc5 = new Hc5Properties();
public int getConnectionTimerRepeat() {
return this.connectionTimerRepeat;
}
@@ -146,4 +152,125 @@ public class FeignHttpClientProperties {
this.connectionTimeout = connectionTimeout;
}
public Hc5Properties getHc5() {
return hc5;
}
public void setHc5(Hc5Properties hc5) {
this.hc5 = hc5;
}
public static class Hc5Properties {
/**
* Default value for pool concurrency policy.
*/
public static final PoolConcurrencyPolicy DEFAULT_POOL_CONCURRENCY_POLICY = PoolConcurrencyPolicy.STRICT;
/**
* Default value for pool reuse policy.
*/
public static final PoolReusePolicy DEFAULT_POOL_REUSE_POLICY = PoolReusePolicy.FIFO;
/**
* Default value for socket timeout.
*/
public static final int DEFAULT_SOCKET_TIMEOUT = 5;
/**
* Default value for socket timeout unit.
*/
public static final TimeUnit DEFAULT_SOCKET_TIMEOUT_UNIT = TimeUnit.SECONDS;
/**
* Pool concurrency policies.
*/
private PoolConcurrencyPolicy poolConcurrencyPolicy = DEFAULT_POOL_CONCURRENCY_POLICY;
/**
* Pool connection re-use policies.
*/
private PoolReusePolicy poolReusePolicy = DEFAULT_POOL_REUSE_POLICY;
/**
* Default value for socket timeout.
*/
private int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
/**
* Default value for socket timeout unit.
*/
private TimeUnit socketTimeoutUnit = DEFAULT_SOCKET_TIMEOUT_UNIT;
public PoolConcurrencyPolicy getPoolConcurrencyPolicy() {
return this.poolConcurrencyPolicy;
}
public void setPoolConcurrencyPolicy(
PoolConcurrencyPolicy poolConcurrencyPolicy) {
this.poolConcurrencyPolicy = poolConcurrencyPolicy;
}
public PoolReusePolicy getPoolReusePolicy() {
return poolReusePolicy;
}
public void setPoolReusePolicy(PoolReusePolicy poolReusePolicy) {
this.poolReusePolicy = poolReusePolicy;
}
public TimeUnit getSocketTimeoutUnit() {
return socketTimeoutUnit;
}
public void setSocketTimeoutUnit(TimeUnit socketTimeoutUnit) {
this.socketTimeoutUnit = socketTimeoutUnit;
}
public int getSocketTimeout() {
return socketTimeout;
}
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
}
/**
* Enumeration of pool concurrency policies.
*/
public enum PoolConcurrencyPolicy {
/**
* Higher concurrency but with lax connection max limit guarantees.
*/
LAX,
/**
* Strict connection max limit guarantees.
*/
STRICT
}
/**
* Enumeration of pooled connection re-use policies.
*/
public enum PoolReusePolicy {
/**
* Re-use as few connections as possible making it possible for connections to
* become idle and expire.
*/
LIFO,
/**
* Re-use all connections equally preventing them from becoming idle and
* expiring.
*/
FIFO
}
}
}

View File

@@ -26,6 +26,12 @@
"description": "Enables the use of the Apache HTTP Client by Feign.",
"defaultValue": "true"
},
{
"name": "feign.httpclient.hc5.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Apache HTTP Client 5 by Feign.",
"defaultValue": "false"
},
{
"name": "feign.okhttp.enabled",
"type": "java.lang.Boolean",

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Nguyen Ky Thanh
*/
public class FeignHttpClient5ConfigurationTests {
private static void verifyHc4BeansAvailable(ConfigurableApplicationContext context) {
org.apache.http.impl.client.CloseableHttpClient httpClient4 = context
.getBean(org.apache.http.impl.client.CloseableHttpClient.class);
assertThat(httpClient4).isNotNull();
org.apache.http.conn.HttpClientConnectionManager connectionManager4 = context
.getBean(org.apache.http.conn.HttpClientConnectionManager.class);
assertThat(connectionManager4).isInstanceOf(
org.apache.http.impl.conn.PoolingHttpClientConnectionManager.class);
Client client = context.getBean(Client.class);
assertThat(client).isInstanceOf(ApacheHttpClient.class);
}
private static void verifyHc5BeansAvailable(ConfigurableApplicationContext context) {
CloseableHttpClient httpClient = context.getBean(CloseableHttpClient.class);
assertThat(httpClient).isNotNull();
HttpClientConnectionManager connectionManager = context
.getBean(HttpClientConnectionManager.class);
assertThat(connectionManager)
.isInstanceOf(PoolingHttpClientConnectionManager.class);
Client client = context.getBean(Client.class);
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
}
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions("ribbon-loadbalancer-{version:\\d.*}.jar")
public static class WithoutLoadBalancerInClasspath {
@Test
public void verifyHttpClient5AutoConfig() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("feign.httpclient.hc5.enabled=true",
"feign.httpclient.enabled=false")
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
verifyHc5BeansAvailable(context);
if (context != null) {
context.close();
}
}
@Test
public void hc5ShouldWinIfTheBothVersionsAvailable() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("feign.httpclient.hc5.enabled=true",
"feign.httpclient.enabled=true")
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
Client client = context.getBean(Client.class);
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
if (context != null) {
context.close();
}
}
@Test
public void hc4ShouldBeTheDefaultIfHc5NotEnabled() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("feign.httpclient.hc5.enabled=false",
"feign.httpclient.enabled=true")
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
}
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions({ "ribbon-loadbalancer-{version:\\d.*}.jar",
"feign-hc5-{version:\\d.*}.jar", "httpclient5-{version:\\d.*}.jar",
"httpcore5-{version:\\d.*}.jar", "httpcore5-h2-{version:\\d.*}.jar" })
public static class WithoutLoadBalancerAndHc5InClasspath {
@Test
public void hc4ShouldWinEvenHc5ConfigEnabled() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("feign.httpclient.hc5.enabled=true")
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
@Test
public void hc4ShouldBeTheDefault() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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,6 +19,7 @@ package org.springframework.cloud.openfeign.loadbalancer;
import java.util.Map;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import org.junit.jupiter.api.Test;
@@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
*/
class FeignLoadBalancerAutoConfigurationTests {
@@ -73,6 +75,30 @@ class FeignLoadBalancerAutoConfigurationTests {
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldInstantiateHttpFeignClient5WhenEnabled() {
ConfigurableApplicationContext context = initContext(
"spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=false", "feign.okhttp.enabled=false",
"feign.httpclient.hc5.enabled=true",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttp5Client.class);
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldInstantiateHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
ConfigurableApplicationContext context = initContext(
"spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=true", "feign.okhttp.enabled=false",
"feign.httpclient.hc5.enabled=true",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttp5Client.class);
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext(
@@ -102,6 +128,28 @@ class FeignLoadBalancerAutoConfigurationTests {
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClient5WhenEnabled() {
ConfigurableApplicationContext context = initContext(
"spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=false", "feign.okhttp.enabled=false",
"feign.httpclient.hc5.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
ConfigurableApplicationContext context = initContext(
"spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=true", "feign.okhttp.enabled=false",
"feign.httpclient.hc5.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
}
@Test
void shouldNotProcessLoadBalancerConfigurationWhenRibbonEnabled() {
ConfigurableApplicationContext context = initContext(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -50,9 +50,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
webEnvironment = RANDOM_PORT,
value = { "spring.application.name=feignribbonclientpathtest",
"feign.okhttp.enabled=false", "feign.httpclient.enabled=false",
"feign.hystrix.enabled=false", "test.path.prefix=/base/path" // For
// pathWithPlaceholder
// test
"feign.hystrix.enabled=false", "feign.httpclient.hc5.enabled=false",
"test.path.prefix=/base/path" // For pathWithPlaceholder test
})
@DirtiesContext
public class FeignRibbonClientPathTests {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -56,7 +56,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
webEnvironment = RANDOM_PORT,
value = { "spring.application.name=feignclientretrytest",
"feign.okhttp.enabled=false", "feign.httpclient.enabled=false",
"feign.hystrix.enabled=false", "localapp.ribbon.MaxAutoRetries=2",
"feign.hystrix.enabled=false", "feign.httpclient.hc5.enabled=false",
"localapp.ribbon.MaxAutoRetries=2",
"localapp.ribbon.MaxAutoRetriesNextServer=3" })
@DirtiesContext
public class FeignRibbonClientRetryTests {

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2013-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.ribbon;
import java.lang.reflect.Field;
import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Lookup;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Nguyen Ky Thanh
*/
@SpringBootTest(
classes = FeignRibbonHttpClient5ConfigurationTests.FeignRibbonHttpClientConfigurationTestsApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "feign.httpclient.disableSslValidation=true",
"feign.httpclient.hc5.enabled=true", "feign.httpclient.enabled=false" })
@DirtiesContext
class FeignRibbonHttpClient5ConfigurationTests {
@Autowired
private HttpClientConnectionManager connectionManager;
@Autowired
private Client client;
@Test
void disableSslTest() throws Exception {
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(
connectionManager);
assertThat(socketFactoryRegistry.lookup(URIScheme.HTTPS.id)).isNotNull();
assertThat(getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers())
.isNull();
}
@Test
void verifyHttpClient5IsPickedUp() {
assertThat(client).isInstanceOf(LoadBalancerFeignClient.class);
Client delegate = (Client) getField(client, "delegate");
assertThat(delegate).isInstanceOf(ApacheHttp5Client.class);
}
private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup(
HttpClientConnectionManager connectionManager) {
DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator) this
.getField(connectionManager, "connectionOperator");
return (Lookup) getField(connectionOperator, "socketFactoryRegistry");
}
private X509TrustManager getX509TrustManager(
Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory) socketFactoryRegistry
.lookup(URIScheme.HTTPS.id);
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) this
.getField(connectionSocketFactory, "socketFactory");
SSLContextSpi sslContext = (SSLContextSpi) getField(sslSocketFactory, "context");
return (X509TrustManager) getField(sslContext, "trustManager");
}
protected <T> Object getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, target);
return value;
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
static class FeignRibbonHttpClientConfigurationTestsApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class)
.run(args);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 the original author or authors.
* Copyright 2013-2021 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.
@@ -16,6 +16,8 @@
package org.springframework.cloud.openfeign.support;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -23,6 +25,8 @@ import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.PoolConcurrencyPolicy;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.PoolReusePolicy;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -30,9 +34,12 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT;
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT_UNIT;
/**
* @author Ryan Baxter
* @author Nguyen Ky Thanh
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@@ -62,16 +69,32 @@ public class FeignHttpClientPropertiesTests {
.isEqualTo(FeignHttpClientProperties.DEFAULT_DISABLE_SSL_VALIDATION);
assertThat(getProperties().isFollowRedirects())
.isEqualTo(FeignHttpClientProperties.DEFAULT_FOLLOW_REDIRECTS);
assertThat(getProperties().getHc5().getPoolConcurrencyPolicy())
.isEqualTo(PoolConcurrencyPolicy.STRICT);
assertThat(getProperties().getHc5().getPoolReusePolicy())
.isEqualTo(PoolReusePolicy.FIFO);
assertThat(getProperties().getHc5().getSocketTimeout())
.isEqualTo(DEFAULT_SOCKET_TIMEOUT);
assertThat(getProperties().getHc5().getSocketTimeoutUnit())
.isEqualTo(DEFAULT_SOCKET_TIMEOUT_UNIT);
}
@Test
public void testCustomization() {
TestPropertyValues.of("feign.httpclient.maxConnections=2",
"feign.httpclient.connectionTimeout=2",
"feign.httpclient.maxConnectionsPerRoute=2",
"feign.httpclient.timeToLive=2",
"feign.httpclient.disableSslValidation=true",
"feign.httpclient.followRedirects=false").applyTo(this.context);
TestPropertyValues
.of("feign.httpclient.maxConnections=2",
"feign.httpclient.connectionTimeout=2",
"feign.httpclient.maxConnectionsPerRoute=2",
"feign.httpclient.timeToLive=2",
"feign.httpclient.disableSslValidation=true",
"feign.httpclient.followRedirects=false",
"feign.httpclient.disableSslValidation=true",
"feign.httpclient.followRedirects=false",
"feign.httpclient.hc5.poolConcurrencyPolicy=lax",
"feign.httpclient.hc5.poolReusePolicy=lifo",
"feign.httpclient.hc5.socketTimeout=200",
"feign.httpclient.hc5.socketTimeoutUnit=milliseconds")
.applyTo(this.context);
setupContext();
assertThat(getProperties().getMaxConnections()).isEqualTo(2);
assertThat(getProperties().getConnectionTimeout()).isEqualTo(2);
@@ -79,6 +102,13 @@ public class FeignHttpClientPropertiesTests {
assertThat(getProperties().getTimeToLive()).isEqualTo(2L);
assertThat(getProperties().isDisableSslValidation()).isTrue();
assertThat(getProperties().isFollowRedirects()).isFalse();
assertThat(getProperties().getHc5().getPoolConcurrencyPolicy())
.isEqualTo(PoolConcurrencyPolicy.LAX);
assertThat(getProperties().getHc5().getPoolReusePolicy())
.isEqualTo(PoolReusePolicy.LIFO);
assertThat(getProperties().getHc5().getSocketTimeout()).isEqualTo(200);
assertThat(getProperties().getHc5().getSocketTimeoutUnit())
.isEqualTo(TimeUnit.MILLISECONDS);
}
private void setupContext() {