Skip to content

Commit 6b1adda

Browse files
Treat WebClient timeouts the same as Hystrix timeouts
WebClient timeouts from Hystrix-wrapped routes resulted in status code 500 sent back to the client of the gateway, while Hystrix-issued timeouts result in 504 (which is the expected behavior). This change harmonizes the behavior of Hystrix- and WebClient-issued timeouts on Hystrix-wrapped routes to both return 504. Fixes gh-554
1 parent 632f92f commit 6b1adda

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.cloud.gateway.filter.GatewayFilter;
2727
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
28+
import org.springframework.cloud.gateway.support.TimeoutException;
2829
import org.springframework.http.HttpStatus;
2930
import org.springframework.http.server.reactive.ServerHttpRequest;
3031
import org.springframework.util.Assert;
@@ -39,6 +40,7 @@
3940
import com.netflix.hystrix.HystrixObservableCommand.Setter;
4041
import com.netflix.hystrix.exception.HystrixRuntimeException;
4142

43+
import static com.netflix.hystrix.exception.HystrixRuntimeException.FailureType.COMMAND_EXCEPTION;
4244
import static com.netflix.hystrix.exception.HystrixRuntimeException.FailureType.TIMEOUT;
4345
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
4446
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
@@ -52,6 +54,7 @@
5254
/**
5355
* Depends on `spring-cloud-starter-netflix-hystrix`, {@see http://cloud.spring.io/spring-cloud-netflix/}
5456
* @author Spencer Gibb
57+
* @author Michele Mancioppi
5558
*/
5659
public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
5760

@@ -101,7 +104,15 @@ public GatewayFilter apply(Config config) {
101104
}).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
102105
if (throwable instanceof HystrixRuntimeException) {
103106
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
104-
if (e.getFailureType() == TIMEOUT) { //TODO: optionally set status
107+
HystrixRuntimeException.FailureType failureType = e.getFailureType();
108+
109+
if (failureType == TIMEOUT ||
110+
/*
111+
* In this case, the timeout originates from WebClient due to the settings
112+
* of `spring.cloud.gateway.httpclient.responseTimeout`
113+
*/
114+
(failureType == COMMAND_EXCEPTION && e.getCause() instanceof TimeoutException)) {
115+
105116
setResponseStatus(exchange, HttpStatus.GATEWAY_TIMEOUT);
106117
return exchange.getResponse().setComplete();
107118
}

spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@
4747
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
4848

4949
@RunWith(SpringRunner.class)
50-
@SpringBootTest(webEnvironment = RANDOM_PORT)
50+
@SpringBootTest(webEnvironment = RANDOM_PORT,
51+
properties = {
52+
"spring.cloud.gateway.httpclient.responseTimeout=3s",
53+
"hystrix.command.stalling-command.execution.isolation.thread.timeoutInMilliseconds=10000"
54+
})
5155
@DirtiesContext
5256
public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests {
5357

@@ -68,6 +72,20 @@ public void hystrixFilterTimesout() {
6872
.expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
6973
}
7074

75+
/*
76+
* Tests that timeouts bubbling from the underpinning WebClient are treated the same as
77+
* Hystrix timeouts in terms of outside response. (Internally, timeouts from the WebClient
78+
* are seen as command failures and trigger the opening of circuit breakers the same way
79+
* timeouts do; it may be confusing in terms of the Hystrix metrics though)
80+
*/
81+
@Test
82+
public void hystrixTimeoutFromWebClient() {
83+
testClient.get().uri("/delay/10")
84+
.header("Host", "www.hystrixresponsestall.org")
85+
.exchange()
86+
.expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
87+
}
88+
7189
@Test
7290
public void hystrixFilterFallback() {
7391
testClient.get().uri("/delay/3?a=b")
@@ -134,6 +152,14 @@ public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder) {
134152
.filters(f -> f.prefixPath("/httpbin")
135153
.hystrix(config -> {}))
136154
.uri("lb:badservice"))
155+
/*
156+
* This is a route encapsulated in a hystrix command that is ready to wait
157+
* for a response far longer than the underpinning WebClient would.
158+
*/
159+
.route("hystrix_response_stall", r -> r.host("**.hystrixresponsestall.org")
160+
.filters(f -> f.prefixPath("/httpbin")
161+
.hystrix(config -> config.setName("stalling-command")))
162+
.uri(uri))
137163
.build();
138164
}
139165
}

0 commit comments

Comments
 (0)