diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java index 511893e7..c0759e1f 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java @@ -260,17 +260,32 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered { } private Duration getResponseTimeout(Route route) { - Object responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR); - if (responseTimeoutAttr != null && responseTimeoutAttr instanceof Number) { - Long routeResponseTimeout = ((Number) responseTimeoutAttr).longValue(); - if (routeResponseTimeout >= 0) { - return Duration.ofMillis(routeResponseTimeout); - } - else { - return null; + try { + if (route.getMetadata().containsKey(RESPONSE_TIMEOUT_ATTR)) { + Long routeResponseTimeout = getLong(route.getMetadata().get(RESPONSE_TIMEOUT_ATTR)); + if (routeResponseTimeout != null && routeResponseTimeout >= 0) { + return Duration.ofMillis(routeResponseTimeout); + } + else { + return null; + } } } + catch (NumberFormatException e) { + // ignore number format and use global default + } return properties.getResponseTimeout(); } + static Long getLong(Object responseTimeoutAttr) { + Long responseTimeout = null; + if (responseTimeoutAttr instanceof Number) { + responseTimeout = ((Integer) responseTimeoutAttr).longValue(); + } + else if (responseTimeoutAttr != null) { + responseTimeout = Long.parseLong(responseTimeoutAttr.toString()); + } + return responseTimeout; + } + } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java index 220cd869..5c5f913c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java @@ -30,6 +30,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** @@ -51,6 +52,14 @@ class NettyRoutingFilterCompatibleTests extends BaseWebClientTests { assertThat(NettyRoutingFilter.getInteger(5)).isEqualTo(5); } + @Test + void getLongHandlesStringAndNumber() { + assertThat(NettyRoutingFilter.getLong("5")).isEqualTo(5); + assertThat(NettyRoutingFilter.getLong(5)).isEqualTo(5); + assertThat(NettyRoutingFilter.getLong(null)).isNull(); + assertThatThrownBy(() -> NettyRoutingFilter.getLong("notanumber")).isInstanceOf(NumberFormatException.class); + } + @Test void shouldApplyResponseTimeoutPerRoute() { testClient.get().uri("/route/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java index dfc694b4..e7e96113 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java @@ -120,6 +120,12 @@ public class NettyRoutingFilterIntegrationTests extends BaseWebClientTests { .hasMessageContaining("Timeout on blocking read for 5000000000 NANOSECONDS"); } + @Test + public void shouldApplyResponseTimeoutForPlaceholder() { + testClient.get().uri("/responseheaders/200").header("Host", "www.responsetimeoutplaceholder.org").exchange() + .expectStatus().isEqualTo(HttpStatus.OK); + } + @Test public void shouldApplyGlobalResponseTimeoutForInvalidRouteTimeoutValue() { testClient.get().uri("/invalidRoute/delay/5").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT) diff --git a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml index 8c25d969..8f81445f 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml @@ -2,6 +2,8 @@ test: hostport: httpbin.org:80 uri: lb://testservice +my.timeout: 3000 + server: error: include-message: always @@ -71,6 +73,15 @@ spring: metadata: response-timeout: 1000 + # ===================================== + - id: per_route_response_timeout_placeholder + uri: ${test.uri} + predicates: + - Host=**.responsetimeoutplaceholder.org + - Path=/responseheaders/** + metadata: + response-timeout: ${my.timeout} + # ===================================== # should be last and not follow alphabetical order - id: default_path_to_httpbin