diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java index 1c581c59..defd1162 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java @@ -16,12 +16,14 @@ package org.springframework.cloud.openfeign.encoding; +import feign.Client; import feign.Feign; -import feign.httpclient.ApacheHttpClient; +import okhttp3.OkHttpClient; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.openfeign.FeignAutoConfiguration; @@ -37,8 +39,11 @@ import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(FeignClientEncodingProperties.class) @ConditionalOnClass(Feign.class) -@ConditionalOnBean(ApacheHttpClient.class) +@ConditionalOnBean(Client.class) @ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false) +//The OK HTTP client uses "transparent" compression. +//If the accept-encoding header is present it disable transparent compression +@ConditionalOnMissingBean(OkHttpClient.class) @AutoConfigureAfter(FeignAutoConfiguration.class) public class FeignAcceptGzipEncodingAutoConfiguration { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java index f0d973fd..04229c1b 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/encoding/FeignContentGzipEncodingAutoConfiguration.java @@ -16,12 +16,14 @@ package org.springframework.cloud.openfeign.encoding; +import feign.Client; import feign.Feign; -import feign.httpclient.ApacheHttpClient; +import okhttp3.OkHttpClient; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.openfeign.FeignAutoConfiguration; @@ -37,7 +39,10 @@ import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(FeignClientEncodingProperties.class) @ConditionalOnClass(Feign.class) -@ConditionalOnBean(ApacheHttpClient.class) +@ConditionalOnBean(Client.class) +//The OK HTTP client uses "transparent" compression. +//If the content-encoding header is present it disable transparent compression +@ConditionalOnMissingBean(OkHttpClient.class) @ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false) @AutoConfigureAfter(FeignAutoConfiguration.class) public class FeignContentGzipEncodingAutoConfiguration { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java index db352aa2..6a9a2fa6 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java @@ -28,6 +28,7 @@ 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.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -78,17 +79,31 @@ class HttpClientFeignLoadBalancedConfiguration { } @Bean - public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, - HttpClientConnectionManager httpClientConnectionManager, + @ConditionalOnProperty(value = "feign.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 = "feign.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(); - this.httpClient = httpClientFactory.createBuilder(). - setDefaultRequestConfig(defaultRequestConfig). + CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig). setConnectionManager(httpClientConnectionManager).build(); - return this.httpClient; + return httpClient; } @PreDestroy diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java index fee27f80..f2fd8c3e 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java @@ -22,13 +22,14 @@ import feign.Request; import feign.Response; import java.io.IOException; +import java.net.URI; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; -import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; +import org.springframework.cloud.client.loadbalancer.RibbonRecoveryCallback; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.cloud.netflix.ribbon.RibbonProperties; @@ -40,6 +41,7 @@ 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.StreamUtils; import com.netflix.client.DefaultLoadBalancerRetryHandler; import com.netflix.client.RequestSpecificRetryHandler; import com.netflix.client.config.IClientConfig; @@ -123,22 +125,29 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser Request feignRequest = null; //on retries the policy will choose the server and set it in the context //extract the server and update the request being made - if(retryContext instanceof LoadBalancedRetryContext) { - ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance(); - if(service != null) { - feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest(); + if (retryContext instanceof LoadBalancedRetryContext) { + ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance(); + if (service != null) { + feignRequest = ((RibbonRequest) request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest(); } } - if(feignRequest == null) { + if (feignRequest == null) { feignRequest = request.toRequest(); } Response response = request.client().execute(feignRequest, options); - if(retryPolicy.retryableStatusCode(response.status())) { + if (retryPolicy.retryableStatusCode(response.status())) { + byte[] byteArray = StreamUtils.copyToByteArray(response.body().asInputStream()); response.close(); - throw new RetryableStatusCodeException(RetryableFeignLoadBalancer.this.getClientName(), response.status()); + throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response, + byteArray, request.getUri()); } return new RibbonResponse(request.getUri(), response); } + }, new RibbonRecoveryCallback() { + @Override + protected RibbonResponse createResponse(Response response, URI uri) { + return new RibbonResponse(uri, response); + } }); } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeException.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeException.java new file mode 100644 index 00000000..473c7886 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2018 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.openfeign.ribbon; + +import feign.Response; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; +import org.springframework.util.StreamUtils; + +/** + * A {@link RetryableStatusCodeException} for {@link Response}s + * @author Ryan Baxter + */ +public class RibbonResponseStatusCodeException extends RetryableStatusCodeException { + private Response response; + + public RibbonResponseStatusCodeException(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 response; + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java index 4633b20c..c5b85b50 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringDecoder.java @@ -17,6 +17,8 @@ package org.springframework.cloud.openfeign.support; +import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; + import java.io.IOException; import java.io.InputStream; import java.lang.reflect.ParameterizedType; @@ -101,7 +103,7 @@ public class SpringDecoder implements Decoder { @Override public HttpHeaders getHeaders() { - return FeignUtils.getHttpHeaders(this.response.headers()); + return getHttpHeaders(this.response.headers()); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java index ce6eef48..7525dd0c 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java @@ -38,6 +38,9 @@ import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; +import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders; +import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; + /** * @author Spencer Gibb */ @@ -94,7 +97,7 @@ public class SpringEncoder implements Encoder { request.headers(null); // converters can modify headers, so update the request // with the modified headers - request.headers(FeignUtils.getHeaders(outputMessage.getHeaders())); + request.headers(getHeaders(outputMessage.getHeaders())); // do not use charset for binary data if (messageConverter instanceof ByteArrayHttpMessageConverter) { @@ -121,7 +124,7 @@ public class SpringEncoder implements Encoder { private final HttpHeaders httpHeaders; private FeignOutputMessage(RequestTemplate request) { - httpHeaders = FeignUtils.getHttpHeaders(request.headers()); + httpHeaders = getHttpHeaders(request.headers()); } @Override diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/beans/extra/TestClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/beans/extra/TestClient.java index 1ea2c6cd..e56e753d 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/beans/extra/TestClient.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/beans/extra/TestClient.java @@ -17,12 +17,12 @@ package org.springframework.cloud.openfeign.beans.extra; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.cloud.openfeign.beans.FeignClientTests; +import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @FeignClient(value = "otherapp", qualifier = "uniquequalifier") public interface TestClient { @RequestMapping(method = RequestMethod.GET, value = "/hello") - FeignClientTests.Hello getHello(); + Hello getHello(); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancerTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancerTests.java index 6d9f8dc0..630647fa 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancerTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancerTests.java @@ -28,6 +28,8 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonRequest; +import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonResponse; import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; @@ -93,7 +95,7 @@ public class FeignLoadBalancerTests { this.inspector); Request request = new RequestTemplate().method("GET").append("http://foo/") .request(); - FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, + RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, new URI(request.url())); Response response = Response.create(200, "Test", @@ -101,7 +103,7 @@ public class FeignLoadBalancerTests { when(this.delegate.execute(any(Request.class), any(Options.class))) .thenReturn(response); - FeignLoadBalancer.RibbonResponse resp = this.feignLoadBalancer.execute(ribbonRequest, null); + RibbonResponse resp = this.feignLoadBalancer.execute(ribbonRequest, null); assertThat(resp.getRequestedURI(), is(new URI("http://foo/"))); } @@ -158,7 +160,7 @@ public class FeignLoadBalancerTests { assertThat(request.url(),is(url)); - FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate,request,new URI(request.url())); + RibbonRequest ribbonRequest = new RibbonRequest(this.delegate,request,new URI(request.url())); Request cloneRequest = ribbonRequest.toRequest(); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientTests.java index b72e6eed..9f5909fd 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientTests.java @@ -38,7 +38,6 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancerTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancerTests.java index 8a592eb7..17341945 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancerTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancerTests.java @@ -21,12 +21,17 @@ import feign.Client; import feign.Request; import feign.Response; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; import java.util.List; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -52,6 +57,7 @@ import org.springframework.retry.backoff.BackOffContext; import org.springframework.retry.backoff.BackOffInterruptedException; import org.springframework.retry.backoff.BackOffPolicy; +import com.netflix.client.DefaultLoadBalancerRetryHandler; import com.netflix.client.RequestSpecificRetryHandler; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; @@ -67,6 +73,7 @@ import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; @@ -381,6 +388,64 @@ public class RetryableFeignLoadBalancerTests { assertEquals(1, backOffPolicyFactory.getCount()); } + @Test + public void executeRetryFail() throws Exception { + RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config); + lbContext.setRetryHandler(new DefaultLoadBalancerRetryHandler(1, 0, true)); + SpringClientFactory clientFactory = mock(SpringClientFactory.class); + IClientConfig config = mock(IClientConfig.class); + doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt()); + doReturn(0).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt()); + doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false)); + doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout)); + doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout)); + doReturn("404").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq("")); + doReturn(config).when(clientFactory).getClientConfig(eq("default")); + doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class)); + RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory); + HttpRequest springRequest = mock(HttpRequest.class); + Request feignRequest = Request.create("GET", "http://foo", new HashMap>(), + new byte[]{}, StandardCharsets.UTF_8); + Client client = mock(Client.class); + FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo")); + Response fourOFourResponse = Response.builder().status(404).headers(new HashMap>()) + .body(new Response.Body() { //set content into response + @Override + public Integer length() { + return "test".getBytes().length; + } + + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public InputStream asInputStream() throws IOException { + return new ByteArrayInputStream("test".getBytes()); + } + + @Override + public Reader asReader() throws IOException { + return new InputStreamReader(asInputStream(), "UTF-8"); + } + + @Override + public void close() throws IOException { + } + }).build(); + doReturn(fourOFourResponse).when(client).execute(any(Request.class), any(Request.Options.class)); + MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory(); + RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, backOffPolicyFactory); + FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null); + verify(client, times(2)).execute(any(Request.class), any(Request.Options.class)); + assertEquals(1, backOffPolicyFactory.getCount()); + InputStream inputStream = ribbonResponse.toResponse().body().asInputStream(); + byte[] buf = new byte[100]; + int read = inputStream.read(buf); + Assert.assertThat(new String(buf, 0, read), is("test")); + } + class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy { private int count = 0; diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeExceptionTest.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeExceptionTest.java new file mode 100644 index 00000000..17559294 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeExceptionTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2018 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.openfeign.ribbon; + +import feign.Request; +import feign.Response; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.util.StreamUtils; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ryan Baxter + */ +@RunWith(MockitoJUnitRunner.class) +public class RibbonResponseStatusCodeExceptionTest { + + @Test + public void getResponse() throws Exception { + Map> headers = new HashMap>(); + List fooValues = new ArrayList(); + fooValues.add("bar"); + headers.put("foo", fooValues); + Request request = Request.create("GET", "http://service.com", + new HashMap>(), new byte[]{}, Charset.defaultCharset()); + byte[] body = "foo".getBytes(); + ByteArrayInputStream is = new ByteArrayInputStream(body); + Response response = Response.builder().status(200).reason("Success").request(request).body(is, body.length).headers(headers).build(); + RibbonResponseStatusCodeException ex = new RibbonResponseStatusCodeException("service", response, body, + new URI(request.url())); + assertEquals(200, ex.getResponse().status()); + assertEquals(request, ex.getResponse().request()); + assertEquals("Success", ex.getResponse().reason()); + assertEquals("foo", StreamUtils.copyToString(ex.getResponse().body().asInputStream(), Charset.defaultCharset())); + } +} \ No newline at end of file