diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index 937782f1..ef290045 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -382,11 +382,9 @@ public class FeignClientFactoryBean Feign.Builder builder = feign(context); if (!StringUtils.hasText(url)) { - if (url != null && LOG.isInfoEnabled()) { - LOG.info("The provided URL is empty. Will try picking an instance via load-balancing."); - } - else if (LOG.isDebugEnabled()) { - LOG.debug("URL not provided. Will use LoadBalancer."); + + if (LOG.isInfoEnabled()) { + LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing."); } if (!name.startsWith("http")) { url = "http://" + name; diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java new file mode 100644 index 00000000..c1ac711e --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/LoadBalancerResponseStatusCodeException.java @@ -0,0 +1,46 @@ +/* + * 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.ByteArrayInputStream; +import java.net.URI; + +import feign.Response; + +import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; + +/** + * A {@link RetryableStatusCodeException} for {@link Response}s. + * + * @author Ryan Baxter + */ +public class LoadBalancerResponseStatusCodeException extends RetryableStatusCodeException { + + private final Response response; + + public LoadBalancerResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) { + super(serviceId, response.status(), response, uri); + this.response = Response.builder().body(new ByteArrayInputStream(body), body.length).headers(response.headers()) + .reason(response.reason()).status(response.status()).request(response.request()).build(); + } + + @Override + public Response getResponse() { + return this.response; + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java index 0be4ea3d..6883a179 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java @@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.ResponseData; import org.springframework.cloud.client.loadbalancer.RetryableRequestContext; -import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData; @@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", responseStatus)); } + byte[] byteArray = response.body() == null ? new byte[] {} + : StreamUtils.copyToByteArray(response.body().asInputStream()); response.close(); - throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url())); + throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray, + URI.create(request.url())); } return response; }, new LoadBalancedRecoveryCallback() { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java index a5573434..c8c7de61 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClientTests.java @@ -16,7 +16,10 @@ package org.springframework.cloud.openfeign.loadbalancer; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import feign.Client; import feign.Request; import feign.Response; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -117,6 +121,13 @@ class RetryableFeignBlockingLoadBalancerClientTests { return Response.builder().request(testRequest()).status(status).build(); } + private Response testResponse(int status, String body) { + // ByteArrayInputStream ignores close() and must be wrapped + InputStream reallyCloseable = new BufferedInputStream( + new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))); + return Response.builder().request(testRequest()).status(status).body(reallyCloseable, null).build(); + } + @Test void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException { Request request = testRequest(); @@ -148,6 +159,22 @@ class RetryableFeignBlockingLoadBalancerClientTests { verify(delegate, times(2)).execute(any(), any()); } + @Test + void shouldExposeResponseBodyOnRetry() throws IOException { + properties.getRetry().getRetryableStatusCodes().add(503); + Request request = testRequest(); + when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"), testResponse(503, "foo")); + 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")); + + Response response = feignBlockingLoadBalancerClient.execute(request, new Request.Options()); + + String bodyContent = IOUtils.toString(response.body().asReader(StandardCharsets.UTF_8)); + assertThat(bodyContent).isEqualTo("foo"); + } + @Test void shouldPassCorrectRequestToDelegate() throws IOException { Request request = testRequest();