Browse Source
# Conflicts: # spring-cloud-openfeign-core/pom.xml # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfiguration.java # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/HttpClientFeignLoadBalancerConfiguration.java # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientAutoConfiguration.java # spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java # spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java # spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientPathTests.java # spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientRetryTests.java # spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/FeignHttpClientPropertiesTests.javapull/512/head
14 changed files with 639 additions and 14 deletions
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* 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,146 @@
@@ -0,0 +1,146 @@
|
||||
/* |
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* 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 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.boot.context.properties.EnableConfigurationProperties; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; |
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; |
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; |
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; |
||||
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; |
||||
|
||||
/** |
||||
* 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({ LoadBalancerClient.class, LoadBalancerClientFactory.class }) |
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true") |
||||
@Import(HttpClient5FeignConfiguration.class) |
||||
@EnableConfigurationProperties(LoadBalancerProperties.class) |
||||
class HttpClient5FeignLoadBalancerConfiguration { |
||||
|
||||
@Bean |
||||
@ConditionalOnMissingBean |
||||
@Conditional(OnRetryNotEnabledCondition.class) |
||||
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient, HttpClient httpClient5, |
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) { |
||||
Client delegate = new ApacheHttp5Client(httpClient5); |
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory); |
||||
} |
||||
|
||||
@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, |
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties, |
||||
LoadBalancerClientFactory loadBalancerClientFactory) { |
||||
Client delegate = new ApacheHttp5Client(httpClient5); |
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory, |
||||
properties, loadBalancerClientFactory); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
/* |
||||
* 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(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue