diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/retry-factory.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/retry-factory.adoc
index b9b9abe5..b593ac00 100644
--- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/retry-factory.adoc
+++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/retry-factory.adoc
@@ -12,6 +12,9 @@ The `Retry` `GatewayFilter` factory supports the following parameters:
Retries are performed after a backoff interval of `firstBackoff * (factor ^ n)`, where `n` is the iteration.
If `maxBackoff` is configured, the maximum backoff applied is limited to `maxBackoff`.
If `basedOnPreviousValue` is true, the backoff is calculated by using `prevBackoff * factor`.
+* `jitter`: The configured random jitter for the retries.
+Generating a backoff between `[backoff - backoff*randomFactor, backoff + backoff*randomFactor]`
+* `timeout`: The configured timeout for the retries.
The following defaults are configured for `Retry` filter, if enabled:
@@ -20,6 +23,8 @@ The following defaults are configured for `Retry` filter, if enabled:
* `methods`: GET method
* `exceptions`: `IOException` and `TimeoutException`
* `backoff`: disabled
+* `jitter`: disabled
+* `timeout`: unlimited
The following listing configures a Retry `GatewayFilter`:
@@ -45,6 +50,9 @@ spring:
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
+ jitter:
+ randomFactor: 0.5
+ timeout: 100ms
----
NOTE: When using the retry filter with a `forward:` prefixed URL, the target endpoint should be written carefully so that, in case of an error, it does not do anything that could result in a response being sent to the client and committed.
@@ -79,10 +87,13 @@ spring:
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
+ jitter:
+ randomFactor: 0.5
+ timeout: 100ms
- id: retryshortcut_route
uri: https://example.org
filters:
- - Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false
+ - Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false,0.5,100ms
----
diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml
index 461e4c70..85b3c76d 100644
--- a/spring-cloud-gateway-integration-tests/pom.xml
+++ b/spring-cloud-gateway-integration-tests/pom.xml
@@ -19,7 +19,7 @@
4.3.0-SNAPSHOT
..
-
+
grpc
http2
diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml
index 6f7ff6b3..e9f6b80c 100644
--- a/spring-cloud-gateway-server-mvc/pom.xml
+++ b/spring-cloud-gateway-server-mvc/pom.xml
@@ -136,4 +136,4 @@
test
-
\ No newline at end of file
+
diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
index 814a6ecf..f073796d 100644
--- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
+++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
@@ -30,6 +30,7 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.netty.Connection;
import reactor.retry.Backoff;
+import reactor.retry.Jitter;
import reactor.retry.Repeat;
import reactor.retry.RepeatContext;
import reactor.retry.Retry;
@@ -40,6 +41,7 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.HasRouteId;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.TimeoutException;
+import org.springframework.core.style.ToStringCreator;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
@@ -69,7 +71,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory shortcutFieldOrder() {
return Arrays.asList("retries", "statuses", "methods", "backoff.firstBackoff", "backoff.maxBackoff",
- "backoff.factor", "backoff.basedOnPreviousValue");
+ "backoff.factor", "backoff.basedOnPreviousValue", "jitter.randomFactor", "timeout");
}
@Override
@@ -123,10 +125,16 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory exceptionRetry = null;
if (!retryConfig.getExceptions().isEmpty()) {
Predicate> retryContextPredicate = context -> {
@@ -162,6 +170,14 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory= 0");
+ }
+ }
+
+ public Duration getTimeout() {
+ return timeout;
+ }
+
+ public RetryConfig setTimeout(Duration timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ public JitterConfig getJitter() {
+ return jitter;
+ }
+
+ public RetryConfig setJitter(JitterConfig jitter) {
+ this.jitter = jitter;
+ return this;
+ }
+
+ public RetryConfig setJitter(double randomFactor) {
+ this.jitter = new JitterConfig(randomFactor);
+ return this;
}
public BackoffConfig getBackoff() {
@@ -433,6 +489,47 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory= 0 && randomFactor <= 1,
+ "random factor must be between 0 and 1 (default 0.5)");
+ }
+
+ public JitterConfig() {
+ }
+
+ public JitterConfig(double randomFactor) {
+ this.randomFactor = randomFactor;
+ }
+
+ public double getRandomFactor() {
+ return randomFactor;
+ }
+
+ public void setRandomFactor(double randomFactor) {
+ this.randomFactor = randomFactor;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this).append("randomFactor", randomFactor).toString();
+
+ }
+
}
}
diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java
index 192411b1..fb1439d1 100644
--- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java
+++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java
@@ -109,6 +109,30 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
// @formatter:on
}
+ @Test
+ public void retryWithBackoffJitterTimeout() {
+ // @formatter:off
+ testClient.get()
+ .uri("/retry?key=retry-with-backoff-jitter-timeout&count=3")
+ .header(HttpHeaders.HOST, "www.retrywithbackoffjittertimeout.org")
+ .exchange()
+ .expectStatus().isOk()
+ .expectHeader().value("X-Retry-Count", CoreMatchers.equalTo("3"));
+ // @formatter:on
+ }
+
+ @Test
+ public void retryWithBackoffTimeout() {
+ // backoff > timeout
+ testClient.get()
+ .uri("/retry?key=retry-with-backoff-timeout&count=3")
+ .header(HttpHeaders.HOST, "www.retrywithbackofftimeout.org")
+ .exchange()
+ .expectStatus()
+ .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+ assertThat(TestConfig.map.get("retry-with-backoff-timeout")).isNotNull().hasValue(2);
+ }
+
@Test
public void retryFilterGetJavaDsl() {
testClient.get()
@@ -363,6 +387,21 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
r -> r.host("**.retrywithbackoff.org").filters(f -> f.prefixPath("/httpbin").retry(config -> {
config.setRetries(2).setBackoff(Duration.ofMillis(100), null, 2, true);
})).uri(uri))
+ .route("retry_with_backoff_jitter_timeout_test", r -> r.host("**.retrywithbackoffjittertimeout.org")
+ .filters(f -> f.prefixPath("/httpbin").retry(config -> {
+ config.setRetries(3)
+ .setBackoff(Duration.ofMillis(50), Duration.ofMillis(100), 2, true)
+ .setJitter(0.1)
+ .setTimeout(Duration.ofMillis(1000));
+ }))
+ .uri(uri))
+ .route("retry_with_backoff_timeout_test", r -> r.host("**.retrywithbackofftimeout.org")
+ .filters(f -> f.prefixPath("/httpbin").retry(config -> {
+ config.setRetries(3)
+ .setBackoff(Duration.ofMillis(100), null, 2, true)
+ .setTimeout(Duration.ofMillis(200));
+ }))
+ .uri(uri))
.route("retry_with_loadbalancer",
r -> r.host("**.retrywithloadbalancer.org")