From b11fd5b2f9a8eef54edb3def1586cfbd4d9fcfab Mon Sep 17 00:00:00 2001 From: gminog Date: Tue, 13 Aug 2019 10:35:15 +0000 Subject: [PATCH] In case the failureType coming from Hystrix is SHOSRTCIRCUIT, then HTTP 503 is dispatched (#1230) * In case the failureType coming from Hystrix is SHOSRTCIRCUIT, then ServiceUnavailableException will be thrown. That exception provides HTTP 503 to the original service consumer. Moreover, HystrixGatewayFilterFactoryTests have been enriched so that the aforementioned case is tested and verified. * The hystrixFilterServiceUnavailable test of HystrixGatewayFilterFactoryTests no longer force-closes the circuit in order not to impact any subsequent tests (see also the comment: https://github.com/spring-cloud/spring-cloud-gateway/pull/1230#pullrequestreview-272440126) --- .../factory/HystrixGatewayFilterFactory.java | 3 ++ .../support/ServiceUnavailableException.java | 42 +++++++++++++++++++ .../HystrixGatewayFilterFactoryTests.java | 19 +++++++++ 3 files changed, 64 insertions(+) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java index 8156c77d..1b3a6104 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java @@ -34,6 +34,7 @@ import rx.Subscription; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.support.ServiceUnavailableException; import org.springframework.cloud.gateway.support.TimeoutException; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -134,6 +135,8 @@ public class HystrixGatewayFilterFactory switch (failureType) { case TIMEOUT: return Mono.error(new TimeoutException()); + case SHORTCIRCUIT: + return Mono.error(new ServiceUnavailableException()); case COMMAND_EXCEPTION: { Throwable cause = e.getCause(); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java new file mode 100644 index 00000000..faca510a --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.support; + +import org.springframework.web.bind.annotation.ResponseStatus; + +import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE; + +@ResponseStatus(value = SERVICE_UNAVAILABLE, reason = "Upstream service is temporarily unavailable") +public class ServiceUnavailableException extends Exception { + + public ServiceUnavailableException() { + } + + public ServiceUnavailableException(String message) { + super(message); + } + + /** + * Disables fillInStackTrace for performance reasons. + * @return + */ + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java index 73d3a3cd..a0cf4090 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java @@ -16,6 +16,9 @@ package org.springframework.cloud.gateway.filter.factory; +import com.netflix.config.ConfigurationManager; +import com.netflix.hystrix.Hystrix; +import com.netflix.hystrix.metric.consumer.HealthCountsStream; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,6 +59,22 @@ public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests { .isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())); } + @Test + public void hystrixFilterServiceUnavailable() { + HealthCountsStream.reset(); + Hystrix.reset(); + ConfigurationManager.getConfigInstance() + .setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", true); + + testClient.get().uri("/delay/3").header("Host", "www.hystrixfailure.org") + .exchange().expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + + HealthCountsStream.reset(); + Hystrix.reset(); + ConfigurationManager.getConfigInstance() + .setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", false); + } + /* * Tests that timeouts bubbling from the underpinning WebClient are treated the same * as Hystrix timeouts in terms of outside response. (Internally, timeouts from the