Updates RetryFilterFunctions to support HttpMethods

This commit is contained in:
sgibb
2023-12-05 14:46:00 -05:00
parent bf3f60db94
commit a5e16a65c6
2 changed files with 59 additions and 10 deletions

View File

@@ -26,6 +26,8 @@ import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.retry.RetryContext;
@@ -35,8 +37,8 @@ import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.support.RetryTemplateBuilder;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
public abstract class RetryFilterFunctions {
@@ -56,14 +58,16 @@ public abstract class RetryFilterFunctions {
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
config.getExceptions().forEach(exception -> retryableExceptions.put(exception, true));
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(config.getRetries(), retryableExceptions);
compositeRetryPolicy.setPolicies(
Arrays.asList(simpleRetryPolicy, new HttpStatusRetryPolicy(config)).toArray(new RetryPolicy[0]));
compositeRetryPolicy
.setPolicies(Arrays.asList(simpleRetryPolicy, new HttpRetryPolicy(config)).toArray(new RetryPolicy[0]));
RetryTemplate retryTemplate = retryTemplateBuilder.customPolicy(compositeRetryPolicy).build();
return (request, next) -> retryTemplate.execute(context -> {
ServerResponse serverResponse = next.handle(request);
if (isRetryableStatusCode(serverResponse.statusCode(), config)) {
throw new HttpServerErrorException(serverResponse.statusCode());
if (isRetryableStatusCode(serverResponse.statusCode(), config)
&& isRetryableMethod(request.method(), config)) {
// use this to transfer information to HttpStatusRetryPolicy
throw new RetryException(request, serverResponse);
}
return serverResponse;
});
@@ -73,19 +77,24 @@ public abstract class RetryFilterFunctions {
return config.getSeries().stream().anyMatch(series -> HttpStatus.Series.resolve(httpStatus.value()) == series);
}
public static class HttpStatusRetryPolicy extends NeverRetryPolicy {
private static boolean isRetryableMethod(HttpMethod method, RetryConfig config) {
return config.methods.contains(method);
}
public static class HttpRetryPolicy extends NeverRetryPolicy {
private final RetryConfig config;
public HttpStatusRetryPolicy(RetryConfig config) {
public HttpRetryPolicy(RetryConfig config) {
this.config = config;
}
@Override
public boolean canRetry(RetryContext context) {
// TODO: custom exception
if (context.getLastThrowable() instanceof HttpServerErrorException e) {
return isRetryableStatusCode(e.getStatusCode(), config);
if (context.getLastThrowable() instanceof RetryException e) {
return isRetryableStatusCode(e.getResponse().statusCode(), config)
&& isRetryableMethod(e.getRequest().method(), config);
}
return super.canRetry(context);
}
@@ -99,9 +108,12 @@ public abstract class RetryFilterFunctions {
private Set<HttpStatus.Series> series = new HashSet<>(List.of(HttpStatus.Series.SERVER_ERROR));
private Set<Class<? extends Throwable>> exceptions = new HashSet<>(
List.of(IOException.class, TimeoutException.class, HttpServerErrorException.class));
List.of(IOException.class, TimeoutException.class, RetryException.class));
private Set<HttpMethod> methods = new HashSet<>(List.of(HttpMethod.GET));
// TODO: individual statuses
// TODO: backoff
// TODO: support more Spring Retry policies
public int getRetries() {
@@ -141,6 +153,42 @@ public abstract class RetryFilterFunctions {
return this;
}
public Set<HttpMethod> getMethods() {
return methods;
}
public RetryConfig setMethods(Set<HttpMethod> methods) {
this.methods = methods;
return this;
}
public RetryConfig addMethods(HttpMethod... methods) {
this.methods.addAll(Arrays.asList(methods));
return this;
}
}
private static class RetryException extends NestedRuntimeException {
private final ServerRequest request;
private final ServerResponse response;
RetryException(ServerRequest request, ServerResponse response) {
super(null);
this.request = request;
this.response = response;
}
public ServerRequest getRequest() {
return request;
}
public ServerResponse getResponse() {
return response;
}
}
}

View File

@@ -802,6 +802,7 @@ public class ServerMvcIntegrationTests {
.route(path("/retry"), http())
.before(new LocalServerPortUriResolver())
.filter(retry(3))
//.filter(retry(config -> config.setRetries(3).setSeries(Set.of(HttpStatus.Series.SERVER_ERROR)).setMethods(Set.of(HttpMethod.GET, HttpMethod.POST))))
.filter(prefixPath("/do"))
.build();
// @formatter:on