Remove http client 4 support (#785)

This commit is contained in:
Olga Maciaszek-Sharma
2022-11-10 18:50:12 +01:00
committed by GitHub
parent 6f1fab1ab3
commit d30540d144
29 changed files with 267 additions and 716 deletions

View File

@@ -130,8 +130,12 @@ If none of them is on the classpath, the default feign client is used.
NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it been added to your project if you want to use it.
The OkHttpClient and ApacheHttpClient and ApacheHC5 feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.enabled` or `spring.cloud.openfeign.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.
The OkHttpClient and Apache HttpClient 5 Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.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.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5 and the ones prefixed with `httpclient.okhttp` to OkHttpClient. You can find a full list of properties you can customise in the appendix.
TIP: Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.
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:

View File

@@ -112,11 +112,6 @@
<artifactId>feign-micrometer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hc5</artifactId>

View File

@@ -16,13 +16,10 @@
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.databind.Module;
@@ -31,17 +28,9 @@ import feign.Client;
import feign.Feign;
import feign.Target;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import jakarta.annotation.PreDestroy;
import okhttp3.ConnectionPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -54,8 +43,6 @@ import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor;
@@ -94,8 +81,6 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
FeignEncoderProperties.class })
public class FeignAutoConfiguration {
private static final Log LOG = LogFactory.getLog(FeignAutoConfiguration.class);
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@@ -212,75 +197,6 @@ public class FeignAutoConfiguration {
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
// for load-balanced clients.
@SuppressWarnings("rawtypes")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@@ -328,10 +244,15 @@ public class FeignAutoConfiguration {
}
// the following configuration is for alternate feign clients if
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
// for load-balanced clients.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnMissingBean(org.apache.hc.client5.http.impl.classic.CloseableHttpClient.class)
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true",
matchIfMissing = true)
@Import(org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.class)
protected static class HttpClient5FeignConfiguration {

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2013-2022 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 = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "false",
matchIfMissing = true)
static class HttpClient5Disabled {
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright 2013-2022 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.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import jakarta.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
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 Ryan Baxter
* @author Marcin Grzejszczak
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class);
private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer",
true);
private CloseableHttpClient httpClient;
@Autowired(required = false)
private RegistryBuilder registryBuilder;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.openfeign.compression.response.enabled", havingValue = "true")
public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
return this.httpClient;
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.openfeign.compression.response.enabled", havingValue = "false",
matchIfMissing = true)
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager,
httpClientProperties);
return this.httpClient;
}
private CloseableHttpClient createClient(HttpClientBuilder builder,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig)
.setConnectionManager(httpClientConnectionManager).build();
return httpClient;
}
@PreDestroy
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
}

View File

@@ -53,8 +53,8 @@ import org.springframework.context.annotation.Import;
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
@Bean

View File

@@ -43,11 +43,13 @@ import org.springframework.context.annotation.Import;
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true")
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.hc5.enabled", havingValue = "true",
matchIfMissing = true)
@Import(HttpClient5FeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class HttpClient5FeignLoadBalancerConfiguration {

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2013-2022 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.httpclient.ApacheHttpClient;
import org.apache.http.client.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.LoadBalancerClientsProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
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;
/**
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
* that uses {@link ApacheHttpClient} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "spring.cloud.openfeign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
@Import(HttpClientFeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class HttpClientFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}
@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(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory, transformers);
}
}

View File

@@ -47,7 +47,7 @@ class EnableFeignClientsTests {
@BeforeEach
void setUp() {
context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.properties("debug=true", "spring.cloud.openfeign.httpclient.enabled=false")
.properties("debug=true", "spring.cloud.openfeign.httpclient.hc5.enabled=false")
.sources(EnableFeignClientsTests.PlainConfiguration.class).run();
}

View File

@@ -47,7 +47,7 @@ class FeignAutoConfigurationTests {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(FeignAutoConfiguration.class))
.withPropertyValues("spring.cloud.openfeign.httpclient.enabled=false");
.withPropertyValues("spring.cloud.openfeign.httpclient.hc5.enabled=false");
@Test
void shouldInstantiateDefaultTargeterWhenFeignCircuitBreakerIsDisabled() {

View File

@@ -18,27 +18,22 @@ package org.springframework.cloud.openfeign;
import java.util.Map;
import feign.Client;
import feign.RequestInterceptor;
import feign.httpclient.ApacheHttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration;
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingInterceptor;
import org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration;
import org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
* @author Biju Kunjummen
* @author Olga Maciaszek-Sharma
*/
class FeignCompressionTests {
@@ -49,8 +44,8 @@ class FeignCompressionTests {
"spring.cloud.openfeign.compression.request.enabled=true",
"spring.cloud.openfeign.okhttp.enabled=false")
.withConfiguration(AutoConfigurations.of(FeignAutoConfiguration.class,
FeignContentGzipEncodingAutoConfiguration.class, FeignAcceptGzipEncodingAutoConfiguration.class,
HttpClientConfiguration.class, PlainConfig.class))
FeignContentGzipEncodingAutoConfiguration.class,
FeignAcceptGzipEncodingAutoConfiguration.class))
.run(context -> {
FeignContext feignContext = context.getBean(FeignContext.class);
Map<String, RequestInterceptor> interceptors = feignContext.getInstances("foo",
@@ -63,27 +58,4 @@ class FeignCompressionTests {
});
}
@Configuration(proxyBeanMethods = false)
protected static class PlainConfig {
@Autowired
private Client client;
@Bean
public ApacheHttpClient client() {
/*
* We know our client is an AppacheHttpClient because we disabled the OK HTTP
* client. FeignAcceptGzipEncodingAutoConfiguration won't load unless there is
* a bean of type ApacheHttpClient (not Client) in this test because the bean
* is not yet created and so the application context doesnt know that the
* Client bean is actually an instance of ApacheHttpClient, therefore
* FeignAcceptGzipEncodingAutoConfiguration will not be loaded. We just create
* a bean here of type ApacheHttpClient so that the configuration will be
* loaded correctly.
*/
return (ApacheHttpClient) this.client;
}
}
}

View File

@@ -18,36 +18,26 @@ 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.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
*/
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();
@@ -58,12 +48,9 @@ class FeignHttpClient5ConfigurationTests {
}
@Test
void verifyHttpClient5AutoConfig() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("spring.cloud.openfeign.httpclient.hc5.enabled=true",
"spring.cloud.openfeign.httpclient.enabled=false")
.web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
void shouldInstantiateHttpClient5ByDefaultWhenDependenciesPresent() {
ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
verifyHc5BeansAvailable(context);
@@ -73,65 +60,17 @@ class FeignHttpClient5ConfigurationTests {
}
@Test
void hc5ShouldWinIfTheBothVersionsAvailable() {
void shouldNotInstantiateHttpClient5ByWhenDependenciesPresentButPropertyDisabled() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("spring.cloud.openfeign.httpclient.hc5.enabled=true",
"spring.cloud.openfeign.httpclient.enabled=true")
.web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
.properties("spring.cloud.openfeign.httpclient.hc5.enabled=false").web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
Client client = context.getBean(Client.class);
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(CloseableHttpClient.class));
if (context != null) {
context.close();
}
}
@Test
void hc4ShouldBeTheDefaultIfHc5NotEnabled() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.httpclient.enabled=true")
.web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
@ClassPathExclusions({ "feign-hc5-{version:\\d.*}.jar", "httpclient5-{version:\\d.*}.jar",
"httpcore5-{version:\\d.*}.jar", "httpcore5-h2-{version:\\d.*}.jar" })
static class WithoutLoadBalancerAndHc5InClasspath {
@Test
void hc4ShouldWinEvenHc5ConfigEnabled() {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.properties("spring.cloud.openfeign.httpclient.hc5.enabled=true").web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
@Test
void hc4ShouldBeTheDefault() {
ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
verifyHc4BeansAvailable(context);
if (context != null) {
context.close();
}
}
}
}

View File

@@ -28,6 +28,7 @@ import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
@@ -41,6 +42,8 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ryan Baxter
*/
// TODO: Bring back when there's corresponding HttpClient 5 support in Commons
@Disabled
class FeignHttpClientConfigurationTests {
private ConfigurableApplicationContext context;

View File

@@ -22,7 +22,7 @@ import java.util.Objects;
import feign.Client;
import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
import feign.hc5.ApacheHttp5Client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -159,7 +159,7 @@ class FeignHttpClientUrlTests {
ReflectionUtils.makeAccessible(field);
Client client = (Client) ReflectionUtils.getField(field, feign);
if (target.name().equals("localappurl")) {
assertThat(client).isInstanceOf(ApacheHttpClient.class).as("client was wrong type");
assertThat(client).isInstanceOf(ApacheHttp5Client.class).as("client was wrong type");
}
return feign.target(target);
}

View File

@@ -22,7 +22,7 @@ import java.util.Objects;
import feign.Client;
import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
import feign.hc5.ApacheHttp5Client;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -160,7 +160,7 @@ class FeignHttpClientUrlWithRetryableLoadBalancerTests {
ReflectionUtils.makeAccessible(field);
Client client = (Client) ReflectionUtils.getField(field, feign);
if (target.name().equals("localappurl")) {
assertThat(client).isInstanceOf(ApacheHttpClient.class).as("client was wrong type");
assertThat(client).isInstanceOf(ApacheHttp5Client.class).as("client was wrong type");
}
return feign.target(target);
}

View File

@@ -45,7 +45,8 @@ class FeignOkHttpConfigurationTests {
void setUp() {
this.context = new SpringApplicationBuilder()
.properties("debug=true", "spring.cloud.openfeign.httpclient.disableSslValidation=true",
"spring.cloud.openfeign.okhttp.enabled=true", "spring.cloud.openfeign.httpclient.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=true",
"spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.httpclient.okhttp.read-timeout=9s")
.web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();

View File

@@ -51,9 +51,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Aaron Whiteside
*/
@SpringBootTest(classes = BeansFeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest",
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG",
"spring.cloud.openfeign.httpclient.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false" })
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=false" })
@DirtiesContext
public class BeansFeignClientTests {

View File

@@ -25,15 +25,15 @@ import java.util.List;
import com.google.protobuf.InvalidProtocolBufferException;
import feign.RequestTemplate;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
@@ -58,7 +58,7 @@ import static org.assertj.core.api.Assertions.fail;
* @author Olga Maciaszek-Sharma
*/
@ExtendWith(MockitoExtension.class)
class ProtobufSpringEncoderTest {
class ProtobufSpringEncoderTests {
@Mock
private HttpClient httpClient;
@@ -72,28 +72,30 @@ class ProtobufSpringEncoderTest {
void testProtobuf() throws IOException {
// protobuf convert to request by feign and ProtobufHttpMessageConverter
RequestTemplate requestTemplate = newRequestTemplate();
newEncoder().encode(this.request, Request.class, requestTemplate);
requestTemplate.target("http://example.com");
newEncoder().encode(request, Request.class, requestTemplate);
HttpEntity entity = toApacheHttpEntity(requestTemplate);
byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
assertThat(this.request.toByteArray()).isEqualTo(bytes);
assertThat(request.toByteArray()).isEqualTo(bytes);
org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request
.parseFrom(bytes);
assertThat(copy).isEqualTo(this.request);
assertThat(copy).isEqualTo(request);
}
@Test
void testProtobufWithCharsetWillFail() throws IOException {
// protobuf convert to request by feign and ProtobufHttpMessageConverter
RequestTemplate requestTemplate = newRequestTemplate();
newEncoder().encode(this.request, Request.class, requestTemplate);
requestTemplate.target("http://example.com");
newEncoder().encode(request, Request.class, requestTemplate);
// set a charset
requestTemplate.body(requestTemplate.body(), StandardCharsets.UTF_8);
HttpEntity entity = toApacheHttpEntity(requestTemplate);
byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
// http request-body is different with original protobuf body
assertThat(this.request.toByteArray().length).isNotEqualTo(bytes.length);
assertThat(request.toByteArray().length).isNotEqualTo(bytes.length);
try {
org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request
.parseFrom(bytes);
@@ -117,16 +119,19 @@ class ProtobufSpringEncoderTest {
}
private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate) throws IOException {
final List<HttpUriRequest> request = new ArrayList<>(1);
BDDMockito.given(this.httpClient.execute(ArgumentMatchers.any()))
.will((Answer<HttpResponse>) invocationOnMock -> {
request.add((HttpUriRequest) invocationOnMock.getArguments()[0]);
return new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion("http", 1, 1), 200, null));
final List<ClassicHttpRequest> request = new ArrayList<>(1);
BDDMockito.given(httpClient.execute(ArgumentMatchers.any(), ArgumentMatchers.any(),
ArgumentMatchers.any(HttpContext.class))).will((Answer<HttpResponse>) invocationOnMock -> {
request.add((ClassicHttpRequest) invocationOnMock.getArguments()[1]);
try (ClassicHttpResponse response = new BasicClassicHttpResponse(200)) {
response.setVersion(new ProtocolVersion("http", 1, 1));
return response;
}
});
new ApacheHttpClient(this.httpClient).execute(requestTemplate.resolve(new HashMap<>()).request(),
new ApacheHttp5Client(httpClient).execute(requestTemplate.resolve(new HashMap<>()).request(),
new feign.Request.Options());
HttpUriRequest httpUriRequest = request.get(0);
return ((HttpEntityEnclosingRequestBase) httpUriRequest).getEntity();
ClassicHttpRequest httpUriRequest = request.get(0);
return httpUriRequest.getEntity();
}
private byte[] read(InputStream in, int length) throws IOException {

View File

@@ -20,7 +20,6 @@ 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;
@@ -43,22 +42,15 @@ class FeignLoadBalancerAutoConfigurationTests {
@Test
void shouldInstantiateDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false",
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, Client.Default.class);
}
@Test
void shouldInstantiateHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttpClient.class);
}
@Test
void shouldInstantiateOkHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false",
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false",
"spring.cloud.openfeign.httpclient.okhttp.read-timeout=9s");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
@@ -74,18 +66,8 @@ class FeignLoadBalancerAutoConfigurationTests {
}
@Test
void shouldInstantiateHttpFeignClient5WhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=false", "spring.cloud.openfeign.httpclient.hc5.enabled=true",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttp5Client.class);
}
@Test
void shouldInstantiateHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=true",
"spring.cloud.openfeign.okhttp.enabled=false", "spring.cloud.openfeign.httpclient.hc5.enabled=true",
void shouldInstantiateHttpFeignClient5WhenAvailableAndOkHttpDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.okhttp.enabled=false",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttp5Client.class);
@@ -93,21 +75,14 @@ class FeignLoadBalancerAutoConfigurationTests {
@Test
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false");
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, Client.Default.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext();
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttpClient.class);
}
@Test
void shouldInstantiateRetryableOkHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false",
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, OkHttpClient.class);
@@ -115,16 +90,7 @@ class FeignLoadBalancerAutoConfigurationTests {
@Test
void shouldInstantiateRetryableHttpFeignClient5WhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=false", "spring.cloud.openfeign.httpclient.hc5.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.enabled=true",
"spring.cloud.openfeign.okhttp.enabled=false", "spring.cloud.openfeign.httpclient.hc5.enabled=true");
ConfigurableApplicationContext context = initContext();
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2013-2022 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.test;
// TODO: Bring back when there's corresponding HttpClient 5 support in Commons
//
// import java.io.IOException;
// import java.lang.reflect.Field;
// import java.util.concurrent.TimeUnit;
//
// import feign.Client;
// import feign.hc5.ApacheHttp5Client;
//
// import org.apache.hc.client5.http.classic.HttpClient;
// import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
// import org.apache.hc.client5.http.io.HttpClientConnectionManager;
// import org.apache.http.config.RegistryBuilder;
// import org.junit.jupiter.api.Test;
// import org.mockito.MockingDetails;
// import org.mockito.Mockito;
//
// import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.boot.SpringBootConfiguration;
// import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
// import org.springframework.boot.test.context.SpringBootTest;
// import
// org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
// import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
// import
// org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory;
// import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory;
// import org.springframework.cloud.openfeign.EnableFeignClients;
// import org.springframework.cloud.openfeign.FeignClient;
// import
// org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
// import org.springframework.context.annotation.Bean;
// 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;
// import static org.mockito.ArgumentMatchers.any;
// import static org.mockito.Mockito.doReturn;
// import static org.mockito.Mockito.mock;
// import static org.mockito.Mockito.mockingDetails;
//
/// **
// * @author Ryan Baxter
// * @author Olga Maciaszek-Sharma
// */
// @SpringBootTest(properties = { "spring.cloud.openfeign.okhttp.enabled: false",
// "spring.cloud.loadbalancer.retry.enabled=false" })
// @DirtiesContext
// class ApacheHttpClient5ConfigurationTests {
//
// @Autowired
// ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
//
// @Autowired
// ApacheHttpClientFactory httpClientFactory;
//
// @Autowired
// FeignBlockingLoadBalancerClient feignClient;
//
// @Test
// void testFactories() {
// assertThat(connectionManagerFactory).isInstanceOf(ApacheHttpClientConnectionManagerFactory.class);
// assertThat(connectionManagerFactory)
// .isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientConnectionManagerFactory.class);
// assertThat(httpClientFactory).isInstanceOf(ApacheHttpClientFactory.class);
// assertThat(httpClientFactory)
// .isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientFactory.class);
// }
//
// @Test
// void testHttpClientWithFeign() {
// Client delegate = feignClient.getDelegate();
// assertThat(delegate instanceof ApacheHttp5Client).isTrue();
// ApacheHttp5Client apacheHttpClient = (ApacheHttp5Client) delegate;
// HttpClient httpClient = getField(apacheHttpClient, "client");
// MockingDetails httpClientDetails = mockingDetails(httpClient);
// assertThat(httpClientDetails.isMock()).isTrue();
// }
//
// @SuppressWarnings("unchecked")
// protected <T> T getField(Object target, String name) {
// Field field = ReflectionUtils.findField(target.getClass(), name);
// ReflectionUtils.makeAccessible(field);
// Object value = ReflectionUtils.getField(field, target);
// return (T) value;
// }
//
// @SpringBootConfiguration
// @EnableAutoConfiguration
// @EnableFeignClients(clients = { ApacheHttpClientConfigurationTestApp.FooClient.class })
// static class ApacheHttpClientConfigurationTestApp {
//
// @FeignClient(name = "foo")
// interface FooClient {
//
// }
//
// static class MyApacheHttpClientConnectionManagerFactory
// extends DefaultApacheHttpClientConnectionManagerFactory {
//
// @Override
// public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
// int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive, TimeUnit
// timeUnit,
// RegistryBuilder registry) {
// return mock(PoolingHttpClientConnectionManager.class);
// }
//
// }
//
// static class MyApacheHttpClientFactory extends DefaultApacheHttpClientFactory {
//
// MyApacheHttpClientFactory(HttpClientBuilder builder) {
// super(builder);
// }
//
// @Override
// public HttpClientBuilder createBuilder() {
// CloseableHttpClient client = mock(CloseableHttpClient.class);
// CloseableHttpResponse response = mock(CloseableHttpResponse.class);
// StatusLine statusLine = mock(StatusLine.class);
// doReturn(200).when(statusLine).getStatusCode();
// Mockito.doReturn(statusLine).when(response).getStatusLine();
// Header[] headers = new BasicHeader[0];
// doReturn(headers).when(response).getAllHeaders();
// try {
// Mockito.doReturn(response).when(client).execute(any(HttpUriRequest.class));
// }
// catch (IOException e) {
// e.printStackTrace();
// }
// HttpClientBuilder builder = mock(HttpClientBuilder.class);
// Mockito.doReturn(client).when(builder).build();
// return builder;
// }
//
// }
//
// @Configuration(proxyBeanMethods = false)
// static class MyConfig {
//
// @Bean
// public ApacheHttpClientFactory apacheHttpClientFactory(HttpClientBuilder builder) {
// return new MyApacheHttpClientFactory(builder);
// }
//
// @Bean
// public ApacheHttpClientConnectionManagerFactory connectionManagerFactory() {
// return new MyApacheHttpClientConnectionManagerFactory();
// }
//
// }
//
// }
//
// }

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2013-2022 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.test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.Header;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.junit.jupiter.api.Test;
import org.mockito.MockingDetails;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.context.annotation.Bean;
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;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
/**
* @author Ryan Baxter
* @author Olga Maciaszek-Sharma
*/
@SpringBootTest(properties = { "spring.cloud.openfeign.okhttp.enabled: false",
"spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
class ApacheHttpClientConfigurationTests {
@Autowired
ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
@Autowired
ApacheHttpClientFactory httpClientFactory;
@Autowired
FeignBlockingLoadBalancerClient feignClient;
@Test
void testFactories() {
assertThat(connectionManagerFactory).isInstanceOf(ApacheHttpClientConnectionManagerFactory.class);
assertThat(connectionManagerFactory)
.isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientConnectionManagerFactory.class);
assertThat(httpClientFactory).isInstanceOf(ApacheHttpClientFactory.class);
assertThat(httpClientFactory)
.isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientFactory.class);
}
@Test
void testHttpClientWithFeign() {
Client delegate = feignClient.getDelegate();
assertThat(delegate instanceof ApacheHttpClient).isTrue();
ApacheHttpClient apacheHttpClient = (ApacheHttpClient) delegate;
HttpClient httpClient = getField(apacheHttpClient, "client");
MockingDetails httpClientDetails = mockingDetails(httpClient);
assertThat(httpClientDetails.isMock()).isTrue();
}
@SuppressWarnings("unchecked")
protected <T> T getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, target);
return (T) value;
}
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableFeignClients(clients = { ApacheHttpClientConfigurationTestApp.FooClient.class })
static class ApacheHttpClientConfigurationTestApp {
@FeignClient(name = "foo")
interface FooClient {
}
static class MyApacheHttpClientConnectionManagerFactory
extends DefaultApacheHttpClientConnectionManagerFactory {
@Override
public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive, TimeUnit timeUnit,
RegistryBuilder registry) {
return mock(PoolingHttpClientConnectionManager.class);
}
}
static class MyApacheHttpClientFactory extends DefaultApacheHttpClientFactory {
MyApacheHttpClientFactory(HttpClientBuilder builder) {
super(builder);
}
@Override
public HttpClientBuilder createBuilder() {
CloseableHttpClient client = mock(CloseableHttpClient.class);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
Mockito.doReturn(statusLine).when(response).getStatusLine();
Header[] headers = new BasicHeader[0];
doReturn(headers).when(response).getAllHeaders();
try {
Mockito.doReturn(response).when(client).execute(any(HttpUriRequest.class));
}
catch (IOException e) {
e.printStackTrace();
}
HttpClientBuilder builder = mock(HttpClientBuilder.class);
Mockito.doReturn(client).when(builder).build();
return builder;
}
}
@Configuration(proxyBeanMethods = false)
static class MyConfig {
@Bean
public ApacheHttpClientFactory apacheHttpClientFactory(HttpClientBuilder builder) {
return new MyApacheHttpClientFactory(builder);
}
@Bean
public ApacheHttpClientConnectionManagerFactory connectionManagerFactory() {
return new MyApacheHttpClientConnectionManagerFactory();
}
}
}
}

View File

@@ -48,7 +48,7 @@ import static org.mockito.Mockito.mockingDetails;
*/
@SpringBootTest(properties = { "spring.cloud.openfeign.okhttp.enabled: true",
"spring.cloud.httpclientfactories.ok.enabled: true", "spring.cloud.openfeign.okhttp.enabled: true",
"spring.cloud.openfeign.httpclient.enabled: false", "spring.cloud.loadbalancer.retry.enabled=false" })
"spring.cloud.openfeign.httpclient.hc5.enabled: false", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
class OkHttpClientConfigurationTests {

View File

@@ -47,8 +47,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*/
@SpringBootTest(classes = FeignClientNotPrimaryTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "spring.application.name=feignclientnotprimarytest",
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG",
"spring.cloud.openfeign.httpclient.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false" })
"spring.cloud.openfeign.httpclient.hc5.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false" })
@DirtiesContext
class FeignClientNotPrimaryTests {

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.openfeign.valid;
import java.util.Objects;
import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -89,7 +90,7 @@ class FeignHttpClientTests {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(feign.httpclient.ApacheHttpClient.class);
assertThat(delegate).isInstanceOf(ApacheHttp5Client.class);
}
@Test

View File

@@ -55,7 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@SpringBootTest(classes = FeignOkHttpTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.circuitbreaker.enabled=false",
"spring.cloud.openfeign.httpclient.enabled=false", "spring.cloud.openfeign.okhttp.enabled=true",
"spring.cloud.openfeign.httpclient.hc5.enabled=false", "spring.cloud.openfeign.okhttp.enabled=true",
"spring.cloud.httpclientfactories.ok.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
class FeignOkHttpTests {

View File

@@ -47,8 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@SpringBootTest(classes = IterableParameterTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=iterableparametertest",
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG",
"spring.cloud.openfeign.httpclient.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false",
"spring.cloud.openfeign.httpclient.hc5.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false",
"spring.cloud.openfeign.circuitbreaker.enabled=false" })
@DirtiesContext
class IterableParameterTests {

View File

@@ -96,10 +96,9 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
* @author Olga Maciaszek-Sharma
*/
@SpringBootTest(classes = ValidFeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest",
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG",
"spring.cloud.openfeign.httpclient.enabled=false", "spring.cloud.openfeign.okhttp.enabled=false",
"spring.cloud.openfeign.circuitbreaker.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" })
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.hc5.enabled=false",
"spring.cloud.openfeign.okhttp.enabled=false", "spring.cloud.openfeign.circuitbreaker.enabled=true",
"spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
class ValidFeignClientTests {

View File

@@ -43,7 +43,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Ryan Baxter
*/
@SpringBootTest(classes = FeignClientEnvVarTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.enabled=false",
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.hc5.enabled=false",
"basepackage=org.springframework.cloud.openfeign.testclients" })
@DirtiesContext
class FeignClientEnvVarTests {

View File

@@ -20,7 +20,6 @@ import feign.Client;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
@@ -43,15 +42,13 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@SpringBootTest(classes = FeignClientScanningTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.enabled=false" })
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.httpclient.hc5.enabled=false" })
@DirtiesContext
class FeignClientScanningTests {
@Value("${local.server.port}")
private int port = 0;
@Autowired
private TestClient testClient;