Add Apache HC5 client option (#498)
This commit is contained in:
@@ -24,5 +24,9 @@
|
||||
|feign.httpclient.time-to-live-unit | |
|
||||
|feign.hystrix.enabled | `false` | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
|
||||
|feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.
|
||||
|
||||
|===
|
||||
|feign.httpclient.hc5.enabled | `false` | Enables the use of the Apache HC5 by Feign.
|
||||
|feign.httpclient.hc5.socket-timeout | `5` |
|
||||
|feign.httpclient.hc5.socket-timeout-unit | `seconds` |
|
||||
|feign.httpclient.hc5.pool-reuse-policy | `fifo` | Pooled connection re-use policies
|
||||
|feign.httpclient.hc5.pool-concurrency-policy | `strict` | Pool concurrency policies
|
||||
|===
|
||||
|
||||
@@ -125,8 +125,8 @@ If none of them is in the classpath, the default feign client is used.
|
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports both `spring-cloud-starter-netflix-ribbon` and `spring-cloud-starter-loadbalancer`. However, as they are optional dependencies, you need to make sure the one you want to use has been added to your project.
|
||||
|
||||
The OkHttpClient and ApacheHttpClient feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP.
|
||||
The OkHttpClient and ApacheHttpClient and ApacheHC5 feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` or `feign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP or `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
|
||||
|
||||
Spring Cloud OpenFeign _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client:
|
||||
|
||||
|
||||
@@ -108,6 +108,11 @@
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hc5</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user