Merge remote-tracking branch 'origin/2.2.x'

Fixes gh-286.

# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/DefaultFeignLoadBalancerConfiguration.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/loadbalancer/OkHttpFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java
This commit is contained in:
Olga Maciaszek-Sharma
2020-10-02 15:15:43 +02:00
13 changed files with 694 additions and 206 deletions

View File

@@ -18,9 +18,14 @@ package org.springframework.cloud.openfeign.loadbalancer;
import feign.Client;
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.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
@@ -35,8 +40,21 @@ class DefaultFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), 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(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryableBlockingFeignLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactory);
}
}

View File

@@ -24,9 +24,11 @@ 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.client.loadbalancer.LoadBalancerClient;
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;
@@ -46,9 +48,22 @@ class HttpClientFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
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(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new RetryableBlockingFeignLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory);
}
}

View File

@@ -23,9 +23,11 @@ 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.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -45,9 +47,22 @@ class OkHttpFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
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(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableBlockingFeignLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013-2020 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 org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.retry.support.RetryTemplate;
/**
* A condition that verifies that {@link RetryTemplate} is on the classpath, a
* {@link LoadBalancedRetryFactory} bean is present and
* <code>spring.cloud.loadbalancer.retry.enabled</code> is not set to <code>false</code>.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.6
*/
public class OnRetryNotEnabledCondition extends AnyNestedCondition {
public OnRetryNotEnabledCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class OnNoRetryTemplateCondition {
}
@ConditionalOnMissingBean(LoadBalancedRetryFactory.class)
static class OnRetryFactoryCondition {
}
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false")
static class OnLoadBalancerRetryEnabledCondition {
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2013-2020 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.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
/**
* A {@link Client} implementation that provides Spring Retry support for requests
* load-balanced with Spring Cloud LoadBalancer.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.6
*/
public class RetryableBlockingFeignLoadBalancerClient implements Client {
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
private final Client delegate;
private final LoadBalancerClient loadBalancerClient;
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
public RetryableBlockingFeignLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
final URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(serviceId,
loadBalancerClient);
RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
return retryTemplate.execute(context -> {
Request feignRequest = null;
// On retries the policy will choose the server and set it in the context
// and extract the server and update the request being made
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
ServiceInstance serviceInstance = lbContext.getServiceInstance();
if (serviceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
+ "Reattempting service instance selection");
}
ServiceInstance previousServiceInstance = lbContext.getPreviousServiceInstance();
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
new RetryableRequestContext(previousServiceInstance, request));
serviceInstance = loadBalancerClient.choose(serviceId, lbRequest);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Selected service instance: %s", serviceInstance));
}
lbContext.setServiceInstance(serviceInstance);
}
if (serviceInstance != null) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Using service instance from LoadBalancedRetryContext: %s",
serviceInstance));
}
String reconstructedUrl = loadBalancerClient.reconstructURI(serviceInstance, originalUri)
.toString();
feignRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
request.body(), request.charset(), request.requestTemplate());
}
}
if (feignRequest == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("Service instance was not resolved, executing the original request");
}
feignRequest = request;
}
Response response = delegate.execute(feignRequest, options);
int responseStatus = response.status();
if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrying on status code: %d", responseStatus));
}
response.close();
throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url()));
}
return response;
}, new LoadBalancedRecoveryCallback<Response, Response>() {
@Override
protected Response createResponse(Response response, URI uri) {
return response;
}
});
}
private RetryTemplate buildRetryTemplate(String serviceId, Request request, LoadBalancedRetryPolicy retryPolicy) {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(serviceId);
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(serviceId);
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new InterceptorRetryPolicy(toHttpRequest(request), retryPolicy, loadBalancerClient, serviceId));
return retryTemplate;
}
// Visible for Sleuth instrumentation
public Client getDelegate() {
return delegate;
}
private HttpRequest toHttpRequest(Request request) {
return new HttpRequest() {
@Override
public HttpMethod getMethod() {
return HttpMethod.resolve(request.httpMethod().name());
}
@Override
public String getMethodValue() {
return getMethod().name();
}
@Override
public URI getURI() {
return URI.create(request.url());
}
@Override
public HttpHeaders getHeaders() {
Map<String, List<String>> headers = new HashMap<>();
Map<String, Collection<String>> feignHeaders = request.headers();
for (String key : feignHeaders.keySet()) {
headers.put(key, new ArrayList<>(feignHeaders.get(key)));
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(headers);
return httpHeaders;
}
};
}
}

View File

@@ -23,10 +23,9 @@ import feign.Client;
import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -36,7 +35,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -49,11 +47,11 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
/**
* @author Spencer Gibb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT, value = {
"spring.application.name=feignclienturltest", "feign.hystrix.enabled=false", "feign.okhttp.enabled=false" })
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT,
value = { "spring.application.name=feignclienturltest", "feign.hystrix.enabled=false",
"feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class FeignHttpClientUrlTests {
class FeignHttpClientUrlTests {
static int port;
@@ -66,35 +64,35 @@ public class FeignHttpClientUrlTests {
@Autowired
private BeanUrlClient beanClient;
@BeforeClass
@BeforeAll
public static void beforeClass() {
port = SocketUtils.findAvailableTcpPort();
System.setProperty("server.port", String.valueOf(port));
}
@AfterClass
@AfterAll
public static void afterClass() {
System.clearProperty("server.port");
}
@Test
public void testUrlHttpClient() {
assertThat(this.urlClient).as("UrlClient was null").isNotNull();
Hello hello = this.urlClient.getHello();
void testUrlHttpClient() {
assertThat(urlClient).as("UrlClient was null").isNotNull();
Hello hello = urlClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@Test
public void testBeanUrl() {
Hello hello = this.beanClient.getHello();
void testBeanUrl() {
Hello hello = beanClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@Test
public void testBeanUrlNoProtocol() {
Hello hello = this.beanClientNoProtocol.getHello();
Hello hello = beanClientNoProtocol.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@@ -174,15 +172,15 @@ public class FeignHttpClientUrlTests {
private String message;
public Hello() {
Hello() {
}
public Hello(String message) {
Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
return message;
}
public void setMessage(String message) {
@@ -198,12 +196,12 @@ public class FeignHttpClientUrlTests {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
return Objects.hash(message);
}
}

View File

@@ -21,7 +21,7 @@ import java.util.Map;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
@@ -36,30 +36,54 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Olga Maciaszek-Sharma
*/
public class FeignLoadBalancerAutoConfigurationTests {
class FeignLoadBalancerAutoConfigurationTests {
@Test
public void shouldInstantiateDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("feign.httpclient.enabled=false");
void shouldInstantiateDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("feign.httpclient.enabled=false",
"spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, Client.Default.class);
}
@Test
public void shouldInstantiateHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.ribbon.enabled=false");
void shouldInstantiateHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, ApacheHttpClient.class);
}
@Test
public void shouldInstantiateOkHttpFeignClientWhenEnabled() {
void shouldInstantiateOkHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("feign.httpclient.enabled=false",
"feign.okhttp.enabled=true");
"feign.okhttp.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalanced(context, OkHttpClient.class);
}
@Test
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, Client.Default.class);
}
@Test
void shouldInstantiateRetryableHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.ribbon.enabled=false");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, ApacheHttpClient.class);
}
@Test
void shouldInstantiateRetryableOkHttpFeignClientWhenEnabled() {
ConfigurableApplicationContext context = initContext("spring.cloud.loadbalancer.ribbon.enabled=false",
"feign.httpclient.enabled=false", "feign.okhttp.enabled=true");
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
assertLoadBalancedWithRetries(context, OkHttpClient.class);
}
private ConfigurableApplicationContext initContext(String... properties) {
return new SpringApplicationBuilder().web(WebApplicationType.NONE).properties(properties)
.sources(HttpClientConfiguration.class, LoadBalancerAutoConfiguration.class,
@@ -79,4 +103,14 @@ public class FeignLoadBalancerAutoConfigurationTests {
assertThat(beans.get("feignClient").getDelegate()).isInstanceOf(delegateClass);
}
private void assertLoadBalancedWithRetries(ConfigurableApplicationContext context, Class delegateClass) {
Map<String, RetryableBlockingFeignLoadBalancerClient> retryableBeans = context
.getBeansOfType(RetryableBlockingFeignLoadBalancerClient.class);
assertThat(retryableBeans).hasSize(1);
Map<String, FeignBlockingLoadBalancerClient> beans = context
.getBeansOfType(FeignBlockingLoadBalancerClient.class);
assertThat(beans).isEmpty();
assertThat(retryableBeans.get("feignRetryClient").getDelegate()).isInstanceOf(delegateClass);
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2013-2020 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.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import feign.Client;
import feign.Request;
import feign.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryPolicy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link RetryableBlockingFeignLoadBalancerClient}. Note: the underlying
* {@link BlockingLoadBalancerClient} is already extensively tested in the Spring Cloud
* Commons project, so here we are only testing the interactions between
* {@link RetryableBlockingFeignLoadBalancerClient} and its delegates.
*
* @see <a href=
* "https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
* @author Olga Maciaszek-Sharma
*/
@ExtendWith(MockitoExtension.class)
class RetryableBlockingFeignLoadBalancerClientTests {
private Client delegate = mock(Client.class);
private LoadBalancedRetryFactory retryFactory = mock(LoadBalancedRetryFactory.class);
private BlockingLoadBalancerClient loadBalancerClient = mock(BlockingLoadBalancerClient.class);
private LoadBalancerProperties properties = new LoadBalancerProperties();
private RetryableBlockingFeignLoadBalancerClient feignBlockingLoadBalancerClient = new RetryableBlockingFeignLoadBalancerClient(
delegate, loadBalancerClient, retryFactory);
private ServiceInstance serviceInstance = new DefaultServiceInstance("test-a", "test", "testhost", 80, false);
@BeforeEach
void setUp() {
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(serviceInstance);
}
@Test
void shouldExtractServiceIdFromRequestUrl() throws IOException {
Request request = testRequest();
Response response = testResponse(200);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(loadBalancerClient).choose(eq("test"), any());
verify(loadBalancerClient).reconstructURI(serviceInstance, URI.create("http://test/path"));
verify(delegate).execute(
argThat((Request actualRequest) -> actualRequest.url().equals("http://testhost:80/path")), any());
}
private Response testResponse(int status) {
return Response.builder().request(testRequest()).status(status).build();
}
@Test
void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException {
Request request = testRequest();
Response response = testResponse(503);
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(null);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(delegate).execute(eq(request), any());
}
@Test
void shouldRetryOnRepeatableStatusCode() throws IOException {
properties.getRetry().getRetryableStatusCodes().add(503);
Request request = testRequest();
Response response = testResponse(503);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
feignBlockingLoadBalancerClient.execute(request, new Request.Options());
verify(loadBalancerClient, times(2)).reconstructURI(serviceInstance, URI.create("http://test/path"));
verify(delegate, times(2)).execute(any(), any());
}
@Test
void shouldPassCorrectRequestToDelegate() throws IOException {
Request request = testRequest();
Request.Options options = new Request.Options();
String url = "http://127.0.0.1/path";
ServiceInstance serviceInstance = new DefaultServiceInstance("test-1", "test", "test-host", 8888, false);
when(loadBalancerClient.choose(eq("test"), any())).thenReturn(serviceInstance);
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create(url));
Response response = testResponse(200);
when(delegate.execute(any(), any())).thenReturn(response);
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
feignBlockingLoadBalancerClient.execute(request, options);
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
verify(delegate, times(1)).execute(captor.capture(), eq(options));
Request actualRequest = captor.getValue();
assertThat(actualRequest.httpMethod()).isEqualTo(Request.HttpMethod.GET);
assertThat(actualRequest.url()).isEqualTo(url);
assertThat(actualRequest.headers()).hasSize(1);
assertThat(actualRequest.headers()).containsEntry(HttpHeaders.CONTENT_TYPE,
Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
assertThat(new String(actualRequest.body())).isEqualTo("hello");
}
private Request testRequest() {
return testRequest("test");
}
private Request testRequest(String host) {
return Request.create(Request.HttpMethod.GET, "http://" + host + "/path", testHeaders(), "hello".getBytes(),
StandardCharsets.UTF_8, null);
}
private Map<String, Collection<String>> testHeaders() {
Map<String, Collection<String>> feignHeaders = new HashMap<>();
feignHeaders.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
return feignHeaders;
}
}

View File

@@ -33,8 +33,7 @@ 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.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.mockito.MockingDetails;
import org.mockito.Mockito;
@@ -52,7 +51,6 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalance
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -63,11 +61,11 @@ import static org.mockito.Mockito.mockingDetails;
/**
* @author Ryan Baxter
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(properties = { "feign.okhttp.enabled: false" })
@SpringBootTest(properties = { "feign.okhttp.enabled: false", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class ApacheHttpClientConfigurationTests {
class ApacheHttpClientConfigurationTests {
@Autowired
ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
@@ -79,18 +77,18 @@ public class ApacheHttpClientConfigurationTests {
FeignBlockingLoadBalancerClient feignClient;
@Test
public void testFactories() {
assertThat(this.connectionManagerFactory).isInstanceOf(ApacheHttpClientConnectionManagerFactory.class);
assertThat(this.connectionManagerFactory)
void testFactories() {
assertThat(connectionManagerFactory).isInstanceOf(ApacheHttpClientConnectionManagerFactory.class);
assertThat(connectionManagerFactory)
.isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientConnectionManagerFactory.class);
assertThat(this.httpClientFactory).isInstanceOf(ApacheHttpClientFactory.class);
assertThat(this.httpClientFactory)
assertThat(httpClientFactory).isInstanceOf(ApacheHttpClientFactory.class);
assertThat(httpClientFactory)
.isInstanceOf(ApacheHttpClientConfigurationTestApp.MyApacheHttpClientFactory.class);
}
@Test
public void testHttpClientWithFeign() {
Client delegate = this.feignClient.getDelegate();
void testHttpClientWithFeign() {
Client delegate = feignClient.getDelegate();
assertThat(ApacheHttpClient.class.isInstance(delegate)).isTrue();
ApacheHttpClient apacheHttpClient = (ApacheHttpClient) delegate;
HttpClient httpClient = getField(apacheHttpClient, "client");

View File

@@ -21,8 +21,7 @@ import java.util.concurrent.TimeUnit;
import feign.Client;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.mockito.MockingDetails;
import org.springframework.beans.factory.annotation.Autowired;
@@ -37,7 +36,6 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -46,13 +44,14 @@ import static org.mockito.Mockito.mockingDetails;
/**
* @author Ryan Baxter
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringRunner.class)
@SpringBootTest(properties = { "feign.okhttp.enabled: true", "spring.cloud.httpclientfactories.ok.enabled: true",
"ribbon.eureka.enabled = false", "ribbon.okhttp.enabled: true", "feign.okhttp.enabled: true",
"ribbon.httpclient.enabled: false", "feign.httpclient.enabled: false" })
"ribbon.httpclient.enabled: false", "feign.httpclient.enabled: false",
"spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class OkHttpClientConfigurationTests {
class OkHttpClientConfigurationTests {
@Autowired
OkHttpClientFactory okHttpClientFactory;
@@ -64,16 +63,16 @@ public class OkHttpClientConfigurationTests {
FeignBlockingLoadBalancerClient feignClient;
@Test
public void testFactories() {
assertThat(this.connectionPoolFactory).isInstanceOf(OkHttpClientConnectionPoolFactory.class);
assertThat(this.connectionPoolFactory).isInstanceOf(TestConfig.MyOkHttpClientConnectionPoolFactory.class);
assertThat(this.okHttpClientFactory).isInstanceOf(OkHttpClientFactory.class);
assertThat(this.okHttpClientFactory).isInstanceOf(TestConfig.MyOkHttpClientFactory.class);
void testFactories() {
assertThat(connectionPoolFactory).isInstanceOf(OkHttpClientConnectionPoolFactory.class);
assertThat(connectionPoolFactory).isInstanceOf(TestConfig.MyOkHttpClientConnectionPoolFactory.class);
assertThat(okHttpClientFactory).isInstanceOf(OkHttpClientFactory.class);
assertThat(okHttpClientFactory).isInstanceOf(TestConfig.MyOkHttpClientFactory.class);
}
@Test
public void testHttpClientWithFeign() {
Client delegate = this.feignClient.getDelegate();
void testHttpClientWithFeign() {
Client delegate = feignClient.getDelegate();
assertThat(feign.okhttp.OkHttpClient.class.isInstance(delegate)).isTrue();
feign.okhttp.OkHttpClient okHttpClient = (feign.okhttp.OkHttpClient) delegate;
OkHttpClient httpClient = getField(okHttpClient, "delegate");

View File

@@ -19,8 +19,7 @@ package org.springframework.cloud.openfeign.valid;
import java.util.Objects;
import feign.Client;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -41,7 +40,6 @@ import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
@@ -53,15 +51,13 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FeignHttpClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
"spring.application.name=feignclienttest", "feign.hystrix.enabled=false", "feign.okhttp.enabled=false" })
@SpringBootTest(classes = FeignHttpClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "feign.hystrix.enabled=false",
"feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class FeignHttpClientTests {
@LocalServerPort
private int port = 0;
class FeignHttpClientTests {
@Autowired
private TestClient testClient;
@@ -73,32 +69,32 @@ public class FeignHttpClientTests {
private UserClient userClient;
@Test
public void testSimpleType() {
Hello hello = this.testClient.getHello();
void testSimpleType() {
Hello hello = testClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@Test
public void testPatch() {
ResponseEntity<Void> response = this.testClient.patchHello(new Hello("foo"));
void testPatch() {
ResponseEntity<Void> response = testClient.patchHello(new Hello("foo"));
assertThat(response).isNotNull();
String header = response.getHeaders().getFirst("x-hello");
assertThat(header).isEqualTo("hello world patch");
}
@Test
public void testFeignClientType() throws IllegalAccessException {
assertThat(this.feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) this.feignClient;
void testFeignClientType() {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(feign.httpclient.ApacheHttpClient.class);
}
@Test
public void testFeignInheritanceSupport() {
assertThat(this.userClient).as("UserClient was null").isNotNull();
final User user = this.userClient.getUser(1);
void testFeignInheritanceSupport() {
assertThat(userClient).as("UserClient was null").isNotNull();
final User user = userClient.getUser(1);
assertThat(user).as("Returned user was null").isNotNull();
assertThat(new User("John Smith")).as("Users were different").isEqualTo(user);
}
@@ -167,15 +163,15 @@ public class FeignHttpClientTests {
private String message;
public Hello() {
Hello() {
}
public Hello(String message) {
Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
return message;
}
public void setMessage(String message) {
@@ -191,12 +187,12 @@ public class FeignHttpClientTests {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
return Objects.hash(message);
}
}
@@ -205,15 +201,15 @@ public class FeignHttpClientTests {
private String name;
public User() {
User() {
}
public User(String name) {
User(String name) {
this.name = name;
}
public String getName() {
return this.name;
return name;
}
public void setName(String name) {
@@ -229,12 +225,12 @@ public class FeignHttpClientTests {
return false;
}
User that = (User) o;
return Objects.equals(this.name, that.name);
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(this.name);
return Objects.hash(name);
}
}

View File

@@ -19,11 +19,9 @@ package org.springframework.cloud.openfeign.valid;
import java.util.Objects;
import feign.Client;
import org.junit.Test;
import org.junit.runner.RunWith;
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.context.SpringBootTest.WebEnvironment;
@@ -41,7 +39,6 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
@@ -53,17 +50,14 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignOkHttpTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest", "feign.hystrix.enabled=false",
"feign.httpclient.enabled=false", "feign.okhttp.enabled=true",
"spring.cloud.httpclientfactories.ok.enabled=true" })
"spring.cloud.httpclientfactories.ok.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class FeignOkHttpTests {
@Value("${local.server.port}")
private int port = 0;
class FeignOkHttpTests {
@Autowired
private TestClient testClient;
@@ -75,32 +69,32 @@ public class FeignOkHttpTests {
private UserClient userClient;
@Test
public void testSimpleType() {
Hello hello = this.testClient.getHello();
void testSimpleType() {
Hello hello = testClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
}
@Test
public void testPatch() {
ResponseEntity<Void> response = this.testClient.patchHello(new Hello("foo"));
void testPatch() {
ResponseEntity<Void> response = testClient.patchHello(new Hello("foo"));
assertThat(response).isNotNull();
String header = response.getHeaders().getFirst("x-hello");
assertThat(header).isEqualTo("hello world patch");
}
@Test
public void testFeignClientType() throws IllegalAccessException {
assertThat(this.feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) this.feignClient;
void testFeignClientType() {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(feign.okhttp.OkHttpClient.class);
}
@Test
public void testFeignInheritanceSupport() {
assertThat(this.userClient).as("UserClient was null").isNotNull();
final User user = this.userClient.getUser(1);
void testFeignInheritanceSupport() {
assertThat(userClient).as("UserClient was null").isNotNull();
final User user = userClient.getUser(1);
assertThat(user).as("Returned user was null").isNotNull();
assertThat(new User("John Smith")).as("Users were different").isEqualTo(user);
}
@@ -171,15 +165,15 @@ public class FeignOkHttpTests {
private String message;
public Hello() {
Hello() {
}
public Hello(String message) {
Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
return message;
}
public void setMessage(String message) {
@@ -195,12 +189,12 @@ public class FeignOkHttpTests {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
return Objects.hash(message);
}
}
@@ -209,15 +203,15 @@ public class FeignOkHttpTests {
private String name;
public User() {
User() {
}
public User(String name) {
User(String name) {
this.name = name;
}
public String getName() {
return this.name;
return name;
}
public void setName(String name) {
@@ -233,12 +227,12 @@ public class FeignOkHttpTests {
return false;
}
User that = (User) o;
return Objects.equals(this.name, that.name);
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(this.name);
return Objects.hash(name);
}
}

View File

@@ -40,14 +40,10 @@ import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.SpringBootTest;
@@ -76,7 +72,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -87,7 +82,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Spencer Gibb
@@ -96,14 +91,15 @@ import static org.hamcrest.Matchers.instanceOf;
* @author Halvdan Hoem Grelland
* @author Aaron Whiteside
* @author Darren Foong
* @author Olga Maciaszek-Sharma
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ValidFeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
value = { "spring.application.name=feignclienttest",
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG", "feign.httpclient.enabled=false",
"feign.okhttp.enabled=false", "feign.hystrix.enabled=true" })
"feign.okhttp.enabled=false", "feign.hystrix.enabled=true",
"spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class ValidFeignClientTests {
class ValidFeignClientTests {
public static final String HELLO_WORLD_1 = "hello world 1";
@@ -113,12 +109,6 @@ public class ValidFeignClientTests {
public static final String MYHEADER2 = "myheader2";
@Rule
public ExpectedException expected = ExpectedException.none();
@Value("${local.server.port}")
private int port = 0;
@Autowired
private TestClient testClient;
@@ -146,104 +136,104 @@ public class ValidFeignClientTests {
}
@Test
public void testClient() {
assertThat(this.testClient).as("testClient was null").isNotNull();
assertThat(Proxy.isProxyClass(this.testClient.getClass())).as("testClient is not a java Proxy").isTrue();
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
void testClient() {
assertThat(testClient).as("testClient was null").isNotNull();
assertThat(Proxy.isProxyClass(testClient.getClass())).as("testClient is not a java Proxy").isTrue();
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testClient);
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
}
@Test
public void testRequestMappingClassLevelPropertyReplacement() {
Hello hello = this.testClient.getHelloUsingPropertyPlaceHolder();
void testRequestMappingClassLevelPropertyReplacement() {
Hello hello = testClient.getHelloUsingPropertyPlaceHolder();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(OI_TERRA_2));
}
@Test
public void testSimpleType() {
Hello hello = this.testClient.getHello();
Hello hello = testClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(HELLO_WORLD_1));
}
@Test
public void testOptional() {
Optional<Hello> hello = this.testClient.getOptionalHello();
void testOptional() {
Optional<Hello> hello = testClient.getOptionalHello();
assertThat(hello).isNotNull().isPresent().contains(new Hello(HELLO_WORLD_1));
}
@Test
public void testGenericType() {
List<Hello> hellos = this.testClient.getHellos();
void testGenericType() {
List<Hello> hellos = testClient.getHellos();
assertThat(hellos).as("hellos was null").isNotNull();
assertThat(getHelloList()).as("hellos didn't match").isEqualTo(hellos);
}
@Test
public void testRequestInterceptors() {
List<String> headers = this.testClient.getHelloHeaders();
void testRequestInterceptors() {
List<String> headers = testClient.getHelloHeaders();
assertThat(headers).as("headers was null").isNotNull();
assertThat(headers.contains("myheader1value")).as("headers didn't contain myheader1value").isTrue();
assertThat(headers.contains("myheader2value")).as("headers didn't contain myheader2value").isTrue();
}
@Test
public void testHeaderPlaceholders() {
String header = this.testClient.getHelloHeadersPlaceholders();
void testHeaderPlaceholders() {
String header = testClient.getHelloHeadersPlaceholders();
assertThat(header).as("header was null").isNotNull();
assertThat(header).as("header was wrong").isEqualTo("myPlaceholderHeaderValue");
}
@Test
public void testFeignClientType() throws IllegalAccessException {
assertThat(this.feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) this.feignClient;
void testFeignClientType() {
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(Client.Default.class);
}
@Test
public void testServiceId() {
assertThat(this.testClientServiceId).as("testClientServiceId was null").isNotNull();
final Hello hello = this.testClientServiceId.getHello();
void testServiceId() {
assertThat(testClientServiceId).as("testClientServiceId was null").isNotNull();
final Hello hello = testClientServiceId.getHello();
assertThat(hello).as("The hello response was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(HELLO_WORLD_1));
}
@Test
public void testParams() {
void testParams() {
List<String> list = Arrays.asList("a", "1", "test");
List<String> params = this.testClient.getParams(list);
List<String> params = testClient.getParams(list);
assertThat(params).as("params was null").isNotNull();
assertThat(params.size()).as("params size was wrong").isEqualTo(list.size());
}
@Test
public void testFormattedParams() {
void testFormattedParams() {
List<LocalDate> list = Arrays.asList(LocalDate.of(2001, 1, 1), LocalDate.of(2018, 6, 10));
List<LocalDate> params = this.testClient.getFormattedParams(list);
List<LocalDate> params = testClient.getFormattedParams(list);
assertThat(params).as("params was null").isNotNull();
assertThat(params).as("params not converted correctly").isEqualTo(list);
}
@Test
public void testNoContentResponse() {
ResponseEntity<Void> response = this.testClient.noContent();
void testNoContentResponse() {
ResponseEntity<Void> response = testClient.noContent();
assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong").isEqualTo(HttpStatus.NO_CONTENT);
}
@Test
public void testHeadResponse() {
ResponseEntity<Void> response = this.testClient.head();
void testHeadResponse() {
ResponseEntity<Void> response = testClient.head();
assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong").isEqualTo(HttpStatus.OK);
}
@Test
public void testHttpEntity() {
HttpEntity<Hello> entity = this.testClient.getHelloEntity();
void testHttpEntity() {
HttpEntity<Hello> entity = testClient.getHelloEntity();
assertThat(entity).as("entity was null").isNotNull();
Hello hello = entity.getBody();
assertThat(hello).as("hello was null").isNotNull();
@@ -251,129 +241,130 @@ public class ValidFeignClientTests {
}
@Test
public void testMoreComplexHeader() {
String response = this.testClient.moreComplexContentType("{\"value\":\"OK\"}");
void testMoreComplexHeader() {
String response = testClient.moreComplexContentType("{\"value\":\"OK\"}");
assertThat(response).as("response was null").isNotNull();
assertThat(response).as("didn't respond with {\"value\":\"OK\"}").isEqualTo("{\"value\":\"OK\"}");
}
@Test
public void testDecodeNotFound() {
ResponseEntity<String> response = this.decodingTestClient.notFound();
void testDecodeNotFound() {
ResponseEntity<String> response = decodingTestClient.notFound();
assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong").isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).as("response body was not null").isNull();
}
@Test
public void testOptionalNotFound() {
Optional<String> s = this.decodingTestClient.optional();
void testOptionalNotFound() {
Optional<String> s = decodingTestClient.optional();
assertThat(s).isNotPresent();
}
@Test
public void testConvertingExpander() {
assertThat(this.testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString());
assertThat(this.testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString());
void testConvertingExpander() {
assertThat(testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString());
assertThat(testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString());
assertThat(this.testClient.getToString(new OtherArg("foo"))).isEqualTo("bar");
assertThat(testClient.getToString(new OtherArg("foo"))).isEqualTo("bar");
List<OtherArg> args = new ArrayList<>();
args.add(new OtherArg("foo"));
args.add(new OtherArg("goo"));
List<String> expectedResult = new ArrayList<>();
expectedResult.add("bar");
expectedResult.add("goo");
assertThat(this.testClient.getToString(args)).isEqualTo(expectedResult);
assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
}
@Test
public void namedFeignClientWorks() {
assertThat(this.namedFeignClient).as("namedFeignClient was null").isNotNull();
void namedFeignClientWorks() {
assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull();
}
@Test
public void testSingleRequestPart() {
String response = this.multipartClient.singlePart("abc");
void testSingleRequestPart() {
String response = multipartClient.singlePart("abc");
assertThat(response).isEqualTo("abc");
}
@Test
public void testSinglePojoRequestPart() {
String response = this.multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1));
void testSinglePojoRequestPart() {
String response = multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1));
assertThat(response).isEqualTo(HELLO_WORLD_1);
}
@Test
public void testMultipleRequestParts() {
void testMultipleRequestParts() {
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null, "hello".getBytes());
String response = this.multipartClient.multipart("abc", "123", file);
String response = multipartClient.multipart("abc", "123", file);
assertThat(response).isEqualTo("abc123hello.bin");
}
@Test
public void testMultiplePojoRequestParts() {
void testMultiplePojoRequestParts() {
Hello pojo1 = new Hello(HELLO_WORLD_1);
Hello pojo2 = new Hello(OI_TERRA_2);
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null, "hello".getBytes());
String response = this.multipartClient.multipartPojo("abc", "123", pojo1, pojo2, file);
String response = multipartClient.multipartPojo("abc", "123", pojo1, pojo2, file);
assertThat(response).isEqualTo("abc123hello world 1oi terra 2hello.bin");
}
@Test
public void testRequestPartWithListOfMultipartFiles() {
void testRequestPartWithListOfMultipartFiles() {
List<MultipartFile> multipartFiles = Arrays.asList(
new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes()),
new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes()));
String partNames = this.multipartClient.requestPartListOfMultipartFilesReturnsPartNames(multipartFiles);
String partNames = multipartClient.requestPartListOfMultipartFilesReturnsPartNames(multipartFiles);
assertThat(partNames).isEqualTo("files,files");
String fileNames = this.multipartClient.requestPartListOfMultipartFilesReturnsFileNames(multipartFiles);
String fileNames = multipartClient.requestPartListOfMultipartFilesReturnsFileNames(multipartFiles);
assertThat(fileNames).contains("hello1.bin", "hello2.bin");
}
@Test
public void testRequestPartWithListOfPojosAndListOfMultipartFiles() {
void testRequestPartWithListOfPojosAndListOfMultipartFiles() {
Hello pojo1 = new Hello(HELLO_WORLD_1);
Hello pojo2 = new Hello(OI_TERRA_2);
MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes());
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes());
String response = this.multipartClient.requestPartListOfPojosAndListOfMultipartFiles(
Arrays.asList(pojo1, pojo2), Arrays.asList(file1, file2));
String response = multipartClient.requestPartListOfPojosAndListOfMultipartFiles(Arrays.asList(pojo1, pojo2),
Arrays.asList(file1, file2));
assertThat(response).isEqualTo("hello world 1oi terra 2hello1.binhello2.bin");
}
@Test
public void testRequestBodyWithSingleMultipartFile() {
void testRequestBodyWithSingleMultipartFile() {
String partName = UUID.randomUUID().toString();
MockMultipartFile file1 = new MockMultipartFile(partName, "hello1.bin", null, "hello".getBytes());
String response = this.multipartClient.requestBodySingleMultipartFile(file1);
String response = multipartClient.requestBodySingleMultipartFile(file1);
assertThat(response).isEqualTo(partName);
}
@Test
public void testRequestBodyWithListOfMultipartFiles() {
void testRequestBodyWithListOfMultipartFiles() {
MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes());
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes());
String response = this.multipartClient.requestBodyListOfMultipartFiles(Arrays.asList(file1, file2));
String response = multipartClient.requestBodyListOfMultipartFiles(Arrays.asList(file1, file2));
assertThat(response).contains("file1", "file2");
}
@Test
public void testRequestBodyWithMap() {
void testRequestBodyWithMap() {
MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes());
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes());
Map<String, Object> form = new HashMap<>();
form.put("file1", file1);
form.put("file2", file2);
form.put("hello", "world");
String response = this.multipartClient.requestBodyMap(form);
String response = multipartClient.requestBodyMap(form);
assertThat(response).contains("file1", "file2", "hello");
}
@Test
public void testInvalidMultipartFile() {
MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes());
expected.expect(instanceOf(EncodeException.class));
this.multipartClient.invalid(file);
void testInvalidMultipartFile() {
assertThatExceptionOfType(EncodeException.class).isThrownBy(() -> {
MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes());
multipartClient.invalid(file);
});
}
protected enum Arg {
@@ -525,7 +516,7 @@ public class ValidFeignClientTests {
@Override
public String toString() {
return this.value;
return value;
}
}
@@ -765,15 +756,15 @@ public class ValidFeignClientTests {
private String message;
public Hello() {
Hello() {
}
public Hello(String message) {
Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
return message;
}
public void setMessage(String message) {
@@ -789,12 +780,12 @@ public class ValidFeignClientTests {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
return Objects.hash(message);
}
}