2626import java .util .concurrent .TimeoutException ;
2727import java .util .function .Consumer ;
2828
29+ import org .springframework .core .NestedRuntimeException ;
30+ import org .springframework .http .HttpMethod ;
2931import org .springframework .http .HttpStatus ;
3032import org .springframework .http .HttpStatusCode ;
3133import org .springframework .retry .RetryContext ;
3537import org .springframework .retry .policy .SimpleRetryPolicy ;
3638import org .springframework .retry .support .RetryTemplate ;
3739import org .springframework .retry .support .RetryTemplateBuilder ;
38- import org .springframework .web .client .HttpServerErrorException ;
3940import org .springframework .web .servlet .function .HandlerFilterFunction ;
41+ import org .springframework .web .servlet .function .ServerRequest ;
4042import org .springframework .web .servlet .function .ServerResponse ;
4143
4244public abstract class RetryFilterFunctions {
@@ -56,14 +58,16 @@ public static HandlerFilterFunction<ServerResponse, ServerResponse> retry(Consum
5658 Map <Class <? extends Throwable >, Boolean > retryableExceptions = new HashMap <>();
5759 config .getExceptions ().forEach (exception -> retryableExceptions .put (exception , true ));
5860 SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy (config .getRetries (), retryableExceptions );
59- compositeRetryPolicy . setPolicies (
60- Arrays .asList (simpleRetryPolicy , new HttpStatusRetryPolicy (config )).toArray (new RetryPolicy [0 ]));
61+ compositeRetryPolicy
62+ . setPolicies ( Arrays .asList (simpleRetryPolicy , new HttpRetryPolicy (config )).toArray (new RetryPolicy [0 ]));
6163 RetryTemplate retryTemplate = retryTemplateBuilder .customPolicy (compositeRetryPolicy ).build ();
6264 return (request , next ) -> retryTemplate .execute (context -> {
6365 ServerResponse serverResponse = next .handle (request );
6466
65- if (isRetryableStatusCode (serverResponse .statusCode (), config )) {
66- throw new HttpServerErrorException (serverResponse .statusCode ());
67+ if (isRetryableStatusCode (serverResponse .statusCode (), config )
68+ && isRetryableMethod (request .method (), config )) {
69+ // use this to transfer information to HttpStatusRetryPolicy
70+ throw new RetryException (request , serverResponse );
6771 }
6872 return serverResponse ;
6973 });
@@ -73,19 +77,24 @@ private static boolean isRetryableStatusCode(HttpStatusCode httpStatus, RetryCon
7377 return config .getSeries ().stream ().anyMatch (series -> HttpStatus .Series .resolve (httpStatus .value ()) == series );
7478 }
7579
76- public static class HttpStatusRetryPolicy extends NeverRetryPolicy {
80+ private static boolean isRetryableMethod (HttpMethod method , RetryConfig config ) {
81+ return config .methods .contains (method );
82+ }
83+
84+ public static class HttpRetryPolicy extends NeverRetryPolicy {
7785
7886 private final RetryConfig config ;
7987
80- public HttpStatusRetryPolicy (RetryConfig config ) {
88+ public HttpRetryPolicy (RetryConfig config ) {
8189 this .config = config ;
8290 }
8391
8492 @ Override
8593 public boolean canRetry (RetryContext context ) {
8694 // TODO: custom exception
87- if (context .getLastThrowable () instanceof HttpServerErrorException e ) {
88- return isRetryableStatusCode (e .getStatusCode (), config );
95+ if (context .getLastThrowable () instanceof RetryException e ) {
96+ return isRetryableStatusCode (e .getResponse ().statusCode (), config )
97+ && isRetryableMethod (e .getRequest ().method (), config );
8998 }
9099 return super .canRetry (context );
91100 }
@@ -99,9 +108,12 @@ public static class RetryConfig {
99108 private Set <HttpStatus .Series > series = new HashSet <>(List .of (HttpStatus .Series .SERVER_ERROR ));
100109
101110 private Set <Class <? extends Throwable >> exceptions = new HashSet <>(
102- List .of (IOException .class , TimeoutException .class , HttpServerErrorException .class ));
111+ List .of (IOException .class , TimeoutException .class , RetryException .class ));
112+
113+ private Set <HttpMethod > methods = new HashSet <>(List .of (HttpMethod .GET ));
103114
104115 // TODO: individual statuses
116+ // TODO: backoff
105117 // TODO: support more Spring Retry policies
106118
107119 public int getRetries () {
@@ -141,6 +153,42 @@ public RetryConfig addExceptions(Class<? extends Throwable>... exceptions) {
141153 return this ;
142154 }
143155
156+ public Set <HttpMethod > getMethods () {
157+ return methods ;
158+ }
159+
160+ public RetryConfig setMethods (Set <HttpMethod > methods ) {
161+ this .methods = methods ;
162+ return this ;
163+ }
164+
165+ public RetryConfig addMethods (HttpMethod ... methods ) {
166+ this .methods .addAll (Arrays .asList (methods ));
167+ return this ;
168+ }
169+
170+ }
171+
172+ private static class RetryException extends NestedRuntimeException {
173+
174+ private final ServerRequest request ;
175+
176+ private final ServerResponse response ;
177+
178+ RetryException (ServerRequest request , ServerResponse response ) {
179+ super (null );
180+ this .request = request ;
181+ this .response = response ;
182+ }
183+
184+ public ServerRequest getRequest () {
185+ return request ;
186+ }
187+
188+ public ServerResponse getResponse () {
189+ return response ;
190+ }
191+
144192 }
145193
146194}
0 commit comments