diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java index 6cb7c5aa..62cc05a4 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; import org.springframework.context.annotation.Bean; @@ -126,9 +127,10 @@ public class RibbonClientConfiguration { @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( IClientConfig config, ServerIntrospector serverIntrospector, - ILoadBalancer loadBalancer, RetryHandler retryHandler) { + ILoadBalancer loadBalancer, RetryHandler retryHandler, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( - config, serverIntrospector); + config, serverIntrospector, loadBalancedRetryPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); Monitors.registerObject("Client_" + this.name, client); @@ -147,9 +149,9 @@ public class RibbonClientConfiguration { @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, - RetryHandler retryHandler) { + RetryHandler retryHandler, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, - serverIntrospector); + serverIntrospector, loadBalancedRetryPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); Monitors.registerObject("Client_" + this.name, client); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java index 56f021ea..786d19d1 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java @@ -27,8 +27,14 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; +import org.springframework.cloud.netflix.ribbon.support.RetryableLoadBalancingClient; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @@ -37,11 +43,11 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps /** * @author Christian Lohmann + * @author Ryan Baxter */ //TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient public class RibbonLoadBalancingHttpClient - extends - AbstractLoadBalancingClient { + extends RetryableLoadBalancingClient { @Deprecated public RibbonLoadBalancingHttpClient() { @@ -61,6 +67,11 @@ public class RibbonLoadBalancingHttpClient super(delegate, config, serverIntrospector); } + public RibbonLoadBalancingHttpClient(IClientConfig iClientConfig, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + super(iClientConfig, serverIntrospector, loadBalancedRetryPolicyFactory); + } + protected HttpClient createDelegate(IClientConfig config) { return HttpClientBuilder.create() // already defaults to 0 in builder, so resetting to 0 won't hurt @@ -73,7 +84,7 @@ public class RibbonLoadBalancingHttpClient } @Override - public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request, + public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { final RequestConfig.Builder builder = RequestConfig.custom(); IClientConfig config = configOverride != null ? configOverride : this.config; @@ -85,16 +96,32 @@ public class RibbonLoadBalancingHttpClient CommonClientConfigKey.FollowRedirects, this.followRedirects)); final RequestConfig requestConfig = builder.build(); - - if (isSecure(configOverride)) { - final URI secureUri = UriComponentsBuilder.fromUri(request.getUri()) - .scheme("https").build().toUri(); - request = request.withNewUri(secureUri); - } - - final HttpUriRequest httpUriRequest = request.toRequest(requestConfig); - final HttpResponse httpResponse = this.delegate.execute(httpUriRequest); - return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); + return this.executeWithRetry(request, new RetryCallback() { + @Override + public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception { + //on retries the policy will choose the server and set it in the context + //extract the server and update the request being made + RibbonApacheHttpRequest newRequest = request; + if(context instanceof LoadBalancedRetryContext) { + ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance(); + if(service != null) { + //Reconstruct the request URI using the host and port set in the retry context + newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(), + newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(), + newRequest.getURI().getPath(), newRequest.getURI().getQuery(), + newRequest.getURI().getFragment())); + } + } + if (isSecure(configOverride)) { + final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri()) + .scheme("https").build().toUri(); + newRequest = newRequest.withNewUri(secureUri); + } + HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig); + final HttpResponse httpResponse = RibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest); + return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); + } + }); } @Override @@ -102,10 +129,4 @@ public class RibbonLoadBalancingHttpClient URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server); return super.reconstructURIWithServer(server, uri); } - - @Override - public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { - return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); - } - } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java index 2131cdcd..1179e149 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java @@ -19,8 +19,13 @@ package org.springframework.cloud.netflix.ribbon.okhttp; import java.net.URI; import java.util.concurrent.TimeUnit; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; -import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; +import org.springframework.cloud.netflix.ribbon.support.RetryableLoadBalancingClient; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; import org.springframework.web.util.UriComponentsBuilder; import com.netflix.client.config.CommonClientConfigKey; @@ -36,9 +41,10 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps /** * @author Spencer Gibb + * @author Ryan Baxter */ public class OkHttpLoadBalancingClient - extends AbstractLoadBalancingClient { + extends RetryableLoadBalancingClient { @Deprecated public OkHttpLoadBalancingClient() { @@ -55,6 +61,12 @@ public class OkHttpLoadBalancingClient super(config, serverIntrospector); } + public OkHttpLoadBalancingClient(IClientConfig config, + ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + super(config, serverIntrospector, loadBalancedRetryPolicyFactory); + } + public OkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector) { super(delegate, config, serverIntrospector); @@ -66,21 +78,36 @@ public class OkHttpLoadBalancingClient } @Override - public OkHttpRibbonResponse execute(OkHttpRibbonRequest ribbonRequest, + public OkHttpRibbonResponse execute(final OkHttpRibbonRequest ribbonRequest, final IClientConfig configOverride) throws Exception { - boolean secure = isSecure(configOverride); + return this.executeWithRetry(ribbonRequest, new RetryCallback() { + @Override + public OkHttpRibbonResponse doWithRetry(RetryContext context) throws Exception { + //on retries the policy will choose the server and set it in the context + //extract the server and update the request being made + OkHttpRibbonRequest newRequest = ribbonRequest; + if(context instanceof LoadBalancedRetryContext) { + ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance(); + if(service != null) { + //Reconstruct the request URI using the host and port set in the retry context + newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(), + newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(), + newRequest.getURI().getPath(), newRequest.getURI().getQuery(), + newRequest.getURI().getFragment())); + } + } + if (isSecure(configOverride)) { + final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri()) + .scheme("https").build().toUri(); + newRequest = newRequest.withNewUri(secureUri); + } + OkHttpClient httpClient = getOkHttpClient(configOverride, secure); - if (secure) { - final URI secureUri = UriComponentsBuilder.fromUri(ribbonRequest.getUri()) - .scheme("https").build().toUri(); - ribbonRequest = ribbonRequest.withNewUri(secureUri); - } - - OkHttpClient httpClient = getOkHttpClient(configOverride, secure); - - final Request request = ribbonRequest.toRequest(); - Response response = httpClient.newCall(request).execute(); - return new OkHttpRibbonResponse(response, ribbonRequest.getUri()); + final Request request = newRequest.toRequest(); + Response response = httpClient.newCall(request).execute(); + return new OkHttpRibbonResponse(response, newRequest.getUri()); + } + }); } OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequest.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequest.java index 093ed84e..09e45a8b 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequest.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequest.java @@ -18,18 +18,29 @@ package org.springframework.cloud.netflix.ribbon.support; import com.netflix.client.ClientRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.util.MultiValueMap; import java.net.URI; /** * @author Spencer Gibb + * @author Ryan Baxter */ -public abstract class ContextAwareRequest extends ClientRequest { +public abstract class ContextAwareRequest extends ClientRequest implements HttpRequest { protected final RibbonCommandContext context; + private HttpHeaders httpHeaders; public ContextAwareRequest(RibbonCommandContext context) { this.context = context; + MultiValueMap headers = context.getHeaders(); + this.httpHeaders = new HttpHeaders(); + for(String key : headers.keySet()) { + this.httpHeaders.put(key, headers.get(key)); + } this.uri = context.uri(); this.isRetriable = context.getRetryable(); } @@ -38,6 +49,21 @@ public abstract class ContextAwareRequest extends ClientRequest { return context; } + @Override + public HttpMethod getMethod() { + return HttpMethod.valueOf(context.getMethod()); + } + + @Override + public URI getURI() { + return this.getUri(); + } + + @Override + public HttpHeaders getHeaders() { + return httpHeaders; + } + protected RibbonCommandContext newContext(URI uri) { RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(), this.context.getMethod(), uri.toString(), this.context.getRetryable(), diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java new file mode 100644 index 00000000..5916dfd6 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java @@ -0,0 +1,111 @@ +/* + * + * Copyright 2013-2016 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.netflix.ribbon.support; + +import java.io.IOException; +import org.apache.commons.lang.BooleanUtils; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; +import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.http.HttpRequest; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import com.netflix.client.IResponse; +import com.netflix.client.RequestSpecificRetryHandler; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + +/** + * A load balancing client which uses Spring Retry to retry failed requests. + * @author Ryan Baxter + */ +public abstract class RetryableLoadBalancingClient + extends AbstractLoadBalancingClient implements ServiceInstanceChooser { + + protected LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = + new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); + + @Deprecated + public RetryableLoadBalancingClient() { + super(); + } + + @Deprecated + public RetryableLoadBalancingClient(final ILoadBalancer lb) { + super(lb); + } + + public RetryableLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector) { + super(config, serverIntrospector); + } + + public RetryableLoadBalancingClient(D delegate, IClientConfig config, ServerIntrospector serverIntrospector) { + super(delegate, config, serverIntrospector); + } + + public RetryableLoadBalancingClient(IClientConfig iClientConfig, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + this(iClientConfig, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + } + + /** + * Executes a {@link S} using Spring Retry. + * @param request The request to execute. + * @param callback The retry callback to use. + * @return The response. + * @throws Exception Thrown if there is an error making the request and a retry cannot be completed successfully. + */ + protected T executeWithRetry(S request, RetryCallback callback) throws Exception { + LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); + RetryTemplate retryTemplate = new RetryTemplate(); + boolean retryable = request.getContext() == null ? true : + BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true); + retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy() + : new RetryPolicy(request, retryPolicy, this, this.getClientName())); + return retryTemplate.execute(callback); + } + + @Override + public ServiceInstance choose(String serviceId) { + Server server = this.getLoadBalancer().chooseServer(serviceId); + return new RibbonLoadBalancerClient.RibbonServer(serviceId, + server); + } + + @Override + public RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig) { + return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); + } + + static class RetryPolicy extends FeignRetryPolicy { + public RetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) { + super(request, policy, serviceInstanceChooser, serviceName); + } + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java index 76d48c06..44022423 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java @@ -16,36 +16,70 @@ package org.springframework.cloud.netflix.ribbon.apache; +import java.io.IOException; +import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.test.util.ReflectionTestUtils; +import com.netflix.client.DefaultLoadBalancerRetryHandler; +import com.netflix.client.RetryHandler; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Sébastien Nussbaumer + * @author Ryan Baxter */ public class RibbonLoadBalancingHttpClientTests { + private ILoadBalancer loadBalancer; + + @Before + public void setup() { + loadBalancer = mock(AbstractLoadBalancer.class); + doReturn(new Server("foo.com", 8000)).when(loadBalancer).chooseServer(eq("default")); + doReturn(new Server("foo.com", 8000)).when(loadBalancer).chooseServer(eq("service")); + } + + @After + public void teardown() { + loadBalancer = null; + } + @Test public void testRequestConfigUseDefaultsNoOverride() throws Exception { RequestConfig result = getBuiltRequestConfig(UseDefaults.class, null); @@ -129,6 +163,169 @@ public class RibbonLoadBalancingHttpClientTests { assertThat(result.getSocketTimeout(), is (50)); } + @Test + public void testNeverRetry() throws Exception { + ServerIntrospector introspector = mock(ServerIntrospector.class); + HttpClient delegate = mock(HttpClient.class); + HttpResponse response = mock(HttpResponse.class); + doThrow(new IOException("boom")).when(delegate).execute(any(HttpUriRequest.class)); + DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); + clientConfig.setClientName("foo"); + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(delegate, clientConfig, + introspector); + RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + try { + client.execute(request, null); + fail("Expected IOException"); + } catch(IOException e) {} finally { + verify(delegate, times(1)).execute(any(HttpUriRequest.class)); + } + } + + private RibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer, + boolean retryable, boolean retryOnAllOps, + String serviceName, String host, int port, + HttpClient delegate, ILoadBalancer lb) throws Exception { + ServerIntrospector introspector = mock(ServerIntrospector.class); + RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable); + doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName)); + DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); + clientConfig.set(CommonClientConfigKey.OkToRetryOnAllOperations, retryOnAllOps); + clientConfig.set(CommonClientConfigKey.MaxAutoRetriesNextServer, retriesNextServer); + clientConfig.set(CommonClientConfigKey.MaxAutoRetries, retriesSameServer); + clientConfig.setClientName(serviceName); + RibbonLoadBalancerContext context = new RibbonLoadBalancerContext(lb, clientConfig, retryHandler); + SpringClientFactory clientFactory = mock(SpringClientFactory.class); + doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName)); + LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory); + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(clientConfig, introspector, factory); + client.setLoadBalancer(lb); + ReflectionTestUtils.setField(client, "delegate", delegate); + return client; + } + + @Test + public void testRetrySameServerOnly() throws Exception { + int retriesNextServer = 0; + int retriesSameServer = 1; + boolean retryable = true; + boolean retryOnAllOps = false; + String serviceName = "foo"; + String host = serviceName; + int port = 80; + HttpMethod method = HttpMethod.GET; + URI uri = new URI("http://" + host + ":" + port); + HttpClient delegate = mock(HttpClient.class); + final HttpResponse response = mock(HttpResponse.class); + doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); + ILoadBalancer lb = mock(ILoadBalancer.class); + RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + serviceName, host, port, delegate, lb); + RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + doReturn(uri).when(request).getURI(); + doReturn(method).when(request).getMethod(); + doReturn(request).when(request).withNewUri(any(URI.class)); + HttpUriRequest uriRequest = mock(HttpUriRequest.class); + doReturn(uri).when(uriRequest).getURI(); + doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); + RibbonApacheHttpResponse returnedResponse = client.execute(request, null); + verify(delegate, times(2)).execute(any(HttpUriRequest.class)); + verify(lb, times(0)).chooseServer(eq(serviceName)); + } + + @Test + public void testRetryNextServer() throws Exception { + int retriesNextServer = 1; + int retriesSameServer = 1; + boolean retryable = true; + boolean retryOnAllOps = false; + String serviceName = "foo"; + String host = serviceName; + int port = 80; + HttpMethod method = HttpMethod.GET; + URI uri = new URI("http://" + host + ":" + port); + HttpClient delegate = mock(HttpClient.class); + final HttpResponse response = mock(HttpResponse.class); + doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). + when(delegate).execute(any(HttpUriRequest.class)); + ILoadBalancer lb = mock(ILoadBalancer.class); + RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + serviceName, host, port, delegate, lb); + RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + doReturn(uri).when(request).getURI(); + doReturn(method).when(request).getMethod(); + doReturn(request).when(request).withNewUri(any(URI.class)); + HttpUriRequest uriRequest = mock(HttpUriRequest.class); + doReturn(uri).when(uriRequest).getURI(); + doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); + RibbonApacheHttpResponse returnedResponse = client.execute(request, null); + verify(delegate, times(3)).execute(any(HttpUriRequest.class)); + verify(lb, times(1)).chooseServer(eq(serviceName)); + } + + @Test + public void testRetryOnPost() throws Exception { + int retriesNextServer = 1; + int retriesSameServer = 1; + boolean retryable = true; + boolean retryOnAllOps = true; + String serviceName = "foo"; + String host = serviceName; + int port = 80; + HttpMethod method = HttpMethod.POST; + URI uri = new URI("http://" + host + ":" + port); + HttpClient delegate = mock(HttpClient.class); + final HttpResponse response = mock(HttpResponse.class); + doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). + when(delegate).execute(any(HttpUriRequest.class)); + ILoadBalancer lb = mock(ILoadBalancer.class); + RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + serviceName, host, port, delegate, lb); + RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + doReturn(method).when(request).getMethod(); + doReturn(uri).when(request).getURI(); + doReturn(request).when(request).withNewUri(any(URI.class)); + HttpUriRequest uriRequest = mock(HttpUriRequest.class); + doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); + RibbonApacheHttpResponse returnedResponse = client.execute(request, null); + verify(delegate, times(3)).execute(any(HttpUriRequest.class)); + verify(lb, times(1)).chooseServer(eq(serviceName)); + } + + @Test + public void testNoRetryOnPost() throws Exception { + int retriesNextServer = 1; + int retriesSameServer = 1; + boolean retryable = true; + boolean retryOnAllOps = false; + String serviceName = "foo"; + String host = serviceName; + int port = 80; + HttpMethod method = HttpMethod.POST; + URI uri = new URI("http://" + host + ":" + port); + HttpClient delegate = mock(HttpClient.class); + final HttpResponse response = mock(HttpResponse.class); + doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). + when(delegate).execute(any(HttpUriRequest.class)); + ILoadBalancer lb = mock(ILoadBalancer.class); + RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + serviceName, host, port, delegate, lb); + RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + doReturn(method).when(request).getMethod(); + doReturn(uri).when(request).getURI(); + doReturn(request).when(request).withNewUri(any(URI.class)); + HttpUriRequest uriRequest = mock(HttpUriRequest.class); + doReturn(uri).when(uriRequest).getURI(); + doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); + try { + client.execute(request, null); + fail("Expected IOException"); + } catch(IOException e) {} finally { + verify(delegate, times(1)).execute(any(HttpUriRequest.class)); + verify(lb, times(0)).chooseServer(eq(serviceName)); + } + } + @Configuration protected static class UseDefaults { @@ -188,14 +385,21 @@ public class RibbonLoadBalancingHttpClientTests { factory.setApplicationContext(new AnnotationConfigApplicationContext( RibbonAutoConfiguration.class, defaultConfigurationClass)); + String serviceName = "foo"; + String host = serviceName; + int port = 80; + URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); RibbonLoadBalancingHttpClient client = factory.getClient("service", RibbonLoadBalancingHttpClient.class); ReflectionTestUtils.setField(client, "delegate", delegate); + ReflectionTestUtils.setField(client, "lb", loadBalancer); given(delegate.execute(any(HttpUriRequest.class))).willReturn( mock(HttpResponse.class)); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); + doReturn(uri).when(request).getURI(); + doReturn(request).when(request).withNewUri(any(URI.class)); given(request.toRequest(any(RequestConfig.class))).willReturn( mock(HttpUriRequest.class)); @@ -203,7 +407,7 @@ public class RibbonLoadBalancingHttpClientTests { ArgumentCaptor requestConfigCaptor = ArgumentCaptor .forClass(RequestConfig.class); - verify(request).toRequest(requestConfigCaptor.capture()); + verify(request, times(1)).toRequest(requestConfigCaptor.capture()); return requestConfigCaptor.getValue(); } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequestTest.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequestTest.java new file mode 100644 index 00000000..56c9567f --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/support/ContextAwareRequestTest.java @@ -0,0 +1,107 @@ +/* + * + * * Copyright 2013-2016 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.netflix.ribbon.support; + + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +/** + * @author Ryan Baxter + */ +public class ContextAwareRequestTest { + + private RibbonCommandContext context; + private ContextAwareRequest request; + + @Before + public void setUp() throws Exception { + context = mock(RibbonCommandContext.class); + doReturn("GET").when(context).getMethod(); + MultiValueMap headers = new LinkedMultiValueMap<>(); + headers.put("header1", Collections.emptyList()); + headers.put("header2", Arrays.asList("value1", "value2")); + headers.put("header3", Arrays.asList("value1")); + doReturn(headers).when(context).getHeaders(); + doReturn(new URI("http://foo")).when(context).uri(); + doReturn("foo").when(context).getServiceId(); + doReturn(new LinkedMultiValueMap<>()).when(context).getParams(); + request = new TestContextAwareRequest(context); + } + + @After + public void tearDown() throws Exception { + context = null; + request = null; + } + + @Test + public void getContext() throws Exception { + assertEquals(context, request.getContext()); + } + + @Test + public void getMethod() throws Exception { + assertEquals(HttpMethod.GET, request.getMethod()); + } + + @Test + public void getURI() throws Exception { + assertEquals(new URI("http://foo"), request.getURI()); + + RibbonCommandContext badUriContext = mock(RibbonCommandContext.class); + doReturn(new LinkedMultiValueMap()).when(badUriContext).getHeaders(); + doReturn("foobar").when(badUriContext).getUri(); + ContextAwareRequest badUriRequest = new TestContextAwareRequest(badUriContext); + + assertNull(badUriRequest.getURI()); + + } + + @Test + public void getHeaders() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.put("header1", Collections.emptyList()); + headers.put("header2", Arrays.asList("value1", "value2")); + headers.put("header3", Arrays.asList("value1")); + assertEquals(headers, request.getHeaders()); + } + + static class TestContextAwareRequest extends ContextAwareRequest { + + public TestContextAwareRequest(RibbonCommandContext context) { + super(context); + } + } + +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonRetryIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonRetryIntegrationTests.java new file mode 100644 index 00000000..7dfea5a7 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonRetryIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2013-2016 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.netflix.zuul.filters.route.apache; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonRetryIntegrationTestBase; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = RibbonRetryIntegrationTestBase.RetryableTestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "zuul.retryable: false", /* Disable retry by default, have each route enable it */ + "hystrix.command.default.execution.timeout.enabled: false", /* Disable hystrix so its timeout doesnt get in the way */ + "ribbon.ReadTimeout: 1000", /* Make sure ribbon will timeout before the thread is done sleeping */ + "zuul.routes.retryable: /retryable/**", + "zuul.routes.retryable.retryable: true", + "retryable.ribbon.OkToRetryOnAllOperations: true", + "retryable.ribbon.MaxAutoRetries: 1", + "retryable.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.getretryable: /getretryable/**", + "zuul.routes.getretryable.retryable: true", + "getretryable.ribbon.MaxAutoRetries: 1", + "getretryable.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.disableretry: /disableretry/**", + "zuul.routes.disableretry.retryable: false", /* This will override the global */ + "disableretry.ribbon.MaxAutoRetries: 1", + "disableretry.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.globalretrydisabled: /globalretrydisabled/**", + "globalretrydisabled.ribbon.MaxAutoRetries: 1", + "globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1" +}) +@DirtiesContext +public class HttpClientRibbonRetryIntegrationTests extends RibbonRetryIntegrationTestBase { +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonRetryIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonRetryIntegrationTests.java new file mode 100644 index 00000000..858ddc84 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonRetryIntegrationTests.java @@ -0,0 +1,55 @@ +/* + * + * * Copyright 2013-2016 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.netflix.zuul.filters.route.okhttp; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonRetryIntegrationTestBase; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = RibbonRetryIntegrationTestBase.RetryableTestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "zuul.retryable: false", /* Disable retry by default, have each route enable it */ + "ribbon.okhttp.enabled: true", + "hystrix.command.default.execution.timeout.enabled: false", /* Disable hystrix so its timeout doesnt get in the way */ + "ribbon.ReadTimeout: 1000", /* Make sure ribbon will timeout before the thread is done sleeping */ + "zuul.routes.retryable: /retryable/**", + "zuul.routes.retryable.retryable: true", + "retryable.ribbon.OkToRetryOnAllOperations: true", + "retryable.ribbon.MaxAutoRetries: 1", + "retryable.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.getretryable: /getretryable/**", + "zuul.routes.getretryable.retryable: true", + "getretryable.ribbon.MaxAutoRetries: 1", + "getretryable.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.disableretry: /disableretry/**", + "zuul.routes.disableretry.retryable: false", /* This will override the global */ + "disableretry.ribbon.MaxAutoRetries: 1", + "disableretry.ribbon.MaxAutoRetriesNextServer: 1", + "zuul.routes.globalretrydisabled: /globalretrydisabled/**", + "globalretrydisabled.ribbon.MaxAutoRetries: 1", + "globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1" +}) +@DirtiesContext +public class OkHttpRibbonRetryIntegrationTests extends RibbonRetryIntegrationTestBase { +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonRetryIntegrationTestBase.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonRetryIntegrationTestBase.java new file mode 100644 index 00000000..b6d7379b --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonRetryIntegrationTestBase.java @@ -0,0 +1,170 @@ +/* + * + * * Copyright 2013-2016 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.netflix.zuul.filters.route.support; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.cloud.netflix.ribbon.StaticServerList; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ryan Baxter + */ +public abstract class RibbonRetryIntegrationTestBase { + + @Value("${local.server.port}") + protected int port; + + @Before + public void setup() { + String uri = "/resetError"; + new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + } + + + @Test + public void retryable() { + String uri = "/retryable/everyothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + + @Test + public void postRetryOK() { + String uri = "/retryable/posteveryothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.POST, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + + @Test + public void getRetryable() { + String uri = "/getretryable/everyothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + } + + @Test + public void postNotRetryable() { + String uri = "/getretryable/posteveryothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.POST, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } + + @Test + public void disbaleRetry() { + String uri = "/disableretry/everyothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } + + @Test + public void globalRetryDisabled() { + String uri = "/globalretrydisabled/everyothererror"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } + + // Don't use @SpringBootApplication because we don't want to component scan + @Configuration + @EnableAutoConfiguration + @RestController + @EnableZuulProxy + @RibbonClients({ + @RibbonClient(name = "retryable", configuration = RibbonClientConfiguration.class), + @RibbonClient(name = "disableretry", configuration = RibbonClientConfiguration.class), + @RibbonClient(name = "globalretrydisabled", configuration = RibbonClientConfiguration.class), + @RibbonClient(name = "getretryable", configuration = RibbonClientConfiguration.class)}) + public static class RetryableTestConfig { + + private boolean error = true; + + @RequestMapping("/resetError") + public void resetError() { + error = true; + } + + @RequestMapping("/everyothererror") + public ResponseEntity timeout() { + boolean shouldError = error; + error = !error; + try { + if(shouldError) { + Thread.sleep(80000); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + return new ResponseEntity("no error", HttpStatus.OK); + } + + @RequestMapping(path = "/posteveryothererror", method = RequestMethod.POST) + public ResponseEntity postTimeout() { + return timeout(); + } + + } + + @Configuration + public static class RibbonClientConfiguration { + + @Value("${local.server.port}") + private int port; + + @Bean + public ServerList ribbonServerList() { + return new StaticServerList<>(new Server("localhost", this.port)); + } + + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java index 58bd1806..4a91135c 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java @@ -375,7 +375,6 @@ public abstract class ZuulProxyTestBase { e.printStackTrace(); } return "slow"; - } @Bean @@ -506,8 +505,8 @@ public abstract class ZuulProxyTestBase { @Bean public ServerList ribbonServerList() { - return new StaticServerList<>(new Server("localhost", this.port)); - } + return new StaticServerList<>(new Server("localhost", this.port)); + } } diff --git a/spring-cloud-netflix-eureka-client/pom.xml b/spring-cloud-netflix-eureka-client/pom.xml index 75c12ccf..a440f19f 100644 --- a/spring-cloud-netflix-eureka-client/pom.xml +++ b/spring-cloud-netflix-eureka-client/pom.xml @@ -123,5 +123,10 @@ spring-boot-starter-test test + + org.springframework.retry + spring-retry + test +