Merge pull request #216 from ryanjbaxter/retry-on-status-codes

Retry requests on specified status codes.  Fixes #215.
This commit is contained in:
Ryan Baxter
2017-05-31 14:17:43 -04:00
committed by GitHub
4 changed files with 57 additions and 5 deletions

View File

@@ -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);
}

View File

@@ -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;
}
});
}

View File

@@ -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));
}
}

View File

@@ -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);