diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java index 3a892aaf..02257ac0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java @@ -103,7 +103,7 @@ public class LoadBalancerAutoConfiguration { @Configuration @ConditionalOnClass(RetryTemplate.class) - static class RetryAutoConfiguration { + public static class RetryAutoConfiguration { @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); @@ -116,13 +116,18 @@ public class LoadBalancerAutoConfiguration { public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); } + } + @Configuration + @ConditionalOnClass(RetryTemplate.class) + public static class RetryInterceptorAutoConfiguration { @Bean + @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, - LoadBalancerRequestFactory requestFactory) { - return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties, + LoadBalancerRequestFactory requestFactory, RetryTemplate retryTemplate) { + return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate, properties, lbRetryPolicyFactory, requestFactory); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java index 2696dc7f..d632b471 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java @@ -68,7 +68,7 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto final URI originalUri = request.getURI(); final String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); - LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName, + final LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName, loadBalancer); retryTemplate.setRetryPolicy( !lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy() @@ -87,9 +87,13 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto if (serviceInstance == null) { serviceInstance = loadBalancer.choose(serviceName); } - return RetryLoadBalancerInterceptor.this.loadBalancer.execute( + ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer.execute( serviceName, serviceInstance, requestFactory.createRequest(request, body, execution)); + if(retryPolicy != null && retryPolicy.retryableStatusCode(response.getRawStatusCode())) { + throw new RetryableStatusCodeException(serviceName, response.getRawStatusCode()); + } + return response; } }); } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java new file mode 100644 index 00000000..d5d4b2db --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryableStatusCodeException.java @@ -0,0 +1,16 @@ +package org.springframework.cloud.client.loadbalancer; + +import java.io.IOException; + +/** + * Exception to be thrown when the status code is deemed to be retryable. + * @author Ryan Baxter + */ +public class RetryableStatusCodeException extends IOException { + + private static final String MESSAGE = "Service %s returned a status code of %d"; + + public RetryableStatusCodeException(String serviceId, int statusCode) { + super(String.format(MESSAGE, serviceId, statusCode)); + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java index 351fa65a..aaeaa768 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTest.java @@ -132,6 +132,33 @@ public class RetryLoadBalancerInterceptorTest { verify(lbRequestFactory).createRequest(request, body, execution); } + @Test + public void interceptRetryOnStatusCode() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ClientHttpResponse clientHttpResponseNotFound = new MockClientHttpResponse(new byte[]{}, HttpStatus.NOT_FOUND); + ClientHttpResponse clientHttpResponseOk = new MockClientHttpResponse(new byte[]{}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.retryableStatusCode(eq(HttpStatus.NOT_FOUND.value()))).thenReturn(true); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true); + InterceptorRetryPolicy interceptorRetryPolicy = new InterceptorRetryPolicy(request, policy, client,"foo"); + LoadBalancedRetryPolicyFactory lbRetryPolicyFactory = mock(LoadBalancedRetryPolicyFactory.class); + when(lbRetryPolicyFactory.create(eq("foo"), any(ServiceInstanceChooser.class))).thenReturn(policy); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"))).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))). + thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); + lbProperties.setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, retryTemplate, lbProperties, lbRetryPolicyFactory, lbRequestFactory); + byte[] body = new byte[]{}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + verify(client, times(2)).execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class)); + assertThat(rsp, is(clientHttpResponseOk)); + verify(retryTemplate, times(1)).setRetryPolicy(eq(interceptorRetryPolicy)); + verify(lbRequestFactory, times(2)).createRequest(request, body, execution); + } + @Test public void interceptRetry() throws Throwable { HttpRequest request = mock(HttpRequest.class);