Add the ability to trip the circuit breaker based on returned status code.
This commit is contained in:
@@ -831,6 +831,55 @@ It is added to the `ServerWebExchange` as the `ServerWebExchangeUtils.CIRCUITBRE
|
||||
For the external controller/handler scenario, headers can be added with exception details.
|
||||
You can find more information on doing so in the <<fallback-headers, FallbackHeaders GatewayFilter Factory section>>.
|
||||
|
||||
[[circuit-breaker-status-codes]]
|
||||
==== Tripping The Circuit Breaker On Status Codes
|
||||
|
||||
In some cases you might want to trip a circuit breaker based on the status code
|
||||
returned from the route it wraps. The circuit breaker config object takes a list of
|
||||
status codes that if returned will cause the the circuit breaker to be tripped. When setting the
|
||||
status codes you want to trip the circuit breaker you can either use a integer with the status code
|
||||
value or the String representation of the `HttpStatus` enumeration.
|
||||
|
||||
.application.yml
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: circuitbreaker_route
|
||||
uri: lb://backing-service:8088
|
||||
predicates:
|
||||
- Path=/consumingServiceEndpoint
|
||||
filters:
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: myCircuitBreaker
|
||||
fallbackUri: forward:/inCaseOfFailureUseThis
|
||||
statusCodes:
|
||||
- 500
|
||||
- "NOT_FOUND"
|
||||
----
|
||||
====
|
||||
|
||||
.Application.java
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public RouteLocator routes(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("circuitbreaker_route", r -> r.path("/consumingServiceEndpoint")
|
||||
.filters(f -> f.circuitBreaker(c -> c.name("myCircuitBreaker").fallbackUri("forward:/inCaseOfFailureUseThis").addStatusCode("INTERNAL_SERVER_ERROR"))
|
||||
.rewritePath("/consumingServiceEndpoint", "/backingServiceEndpoint")).uri("lb://backing-service:8088")
|
||||
.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[fallback-headers]]
|
||||
=== The `FallbackHeaders` `GatewayFilter` Factory
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -40,6 +38,7 @@ import org.springframework.cloud.gateway.event.EnableBodyCachingEvent;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
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.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -48,8 +47,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.removeAlreadyRouted;
|
||||
|
||||
public class RetryGatewayFilterFactory
|
||||
extends AbstractGatewayFilterFactory<RetryGatewayFilterFactory.RetryConfig> {
|
||||
@@ -211,13 +208,12 @@ public class RetryGatewayFilterFactory
|
||||
return exceeds;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
/**
|
||||
* Use {@link ServerWebExchangeUtils#reset(ServerWebExchange)}
|
||||
*/
|
||||
public void reset(ServerWebExchange exchange) {
|
||||
// TODO: what else to do to reset exchange?
|
||||
Set<String> addedHeaders = exchange.getAttributeOrDefault(
|
||||
CLIENT_RESPONSE_HEADER_NAMES, Collections.emptySet());
|
||||
addedHeaders
|
||||
.forEach(header -> exchange.getResponse().getHeaders().remove(header));
|
||||
removeAlreadyRouted(exchange);
|
||||
ServerWebExchangeUtils.reset(exchange);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -27,8 +30,11 @@ import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFac
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.HasRouteId;
|
||||
import org.springframework.cloud.gateway.support.HttpStatusHolder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
@@ -40,6 +46,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.C
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.removeAlreadyRouted;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.reset;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
@@ -85,7 +92,19 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
GatewayFilterChain chain) {
|
||||
return cb.run(chain.filter(exchange), t -> {
|
||||
return cb.run(chain.filter(exchange).doOnSuccess(v -> {
|
||||
Set<HttpStatus> statuses = config.getStatusCodes().stream()
|
||||
.map(HttpStatusHolder::parse)
|
||||
.filter(statusHolder -> statusHolder.getHttpStatus() != null)
|
||||
.map(HttpStatusHolder::getHttpStatus)
|
||||
.collect(Collectors.toSet());
|
||||
if (statuses.contains(exchange.getResponse().getStatusCode())) {
|
||||
HttpStatus status = exchange.getResponse().getStatusCode();
|
||||
exchange.getResponse().setStatusCode(null);
|
||||
reset(exchange);
|
||||
throw new CircuitBreakerStatusCodeException(status);
|
||||
}
|
||||
}), t -> {
|
||||
if (config.getFallbackUri() == null) {
|
||||
return Mono.error(t);
|
||||
}
|
||||
@@ -141,6 +160,8 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends
|
||||
|
||||
private String routeId;
|
||||
|
||||
private Set<String> statusCodes = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void setRouteId(String routeId) {
|
||||
this.routeId = routeId;
|
||||
@@ -179,6 +200,28 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends
|
||||
return name;
|
||||
}
|
||||
|
||||
public Set<String> getStatusCodes() {
|
||||
return statusCodes;
|
||||
}
|
||||
|
||||
public Config setStatusCodes(Set<String> statusCodes) {
|
||||
this.statusCodes = statusCodes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Config addStatusCode(String statusCode) {
|
||||
this.statusCodes.add(statusCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class CircuitBreakerStatusCodeException extends HttpStatusCodeException {
|
||||
|
||||
public CircuitBreakerStatusCodeException(HttpStatus statusCode) {
|
||||
super(statusCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.cloud.gateway.support;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -193,6 +195,15 @@ public final class ServerWebExchangeUtils {
|
||||
return response;
|
||||
}
|
||||
|
||||
public static void reset(ServerWebExchange exchange) {
|
||||
// TODO: what else to do to reset exchange?
|
||||
Set<String> addedHeaders = exchange.getAttributeOrDefault(
|
||||
CLIENT_RESPONSE_HEADER_NAMES, Collections.emptySet());
|
||||
addedHeaders
|
||||
.forEach(header -> exchange.getResponse().getHeaders().remove(header));
|
||||
removeAlreadyRouted(exchange);
|
||||
}
|
||||
|
||||
public static boolean setResponseStatus(ServerWebExchange exchange,
|
||||
HttpStatusHolder statusHolder) {
|
||||
if (exchange.getResponse().isCommitted()) {
|
||||
|
||||
@@ -104,4 +104,22 @@ public abstract class SpringCloudCircuitBreakerFilterFactoryTests
|
||||
.json("{\"from\":\"circuitbreakerfallbackcontroller3\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterStatusCodeFallback() {
|
||||
testClient.get().uri("/status/500")
|
||||
.header("Host", "www.circuitbreakerstatuscode.org").exchange()
|
||||
.expectStatus().isOk().expectBody()
|
||||
.json("{\"from\":\"statusCodeFallbackController\"}");
|
||||
|
||||
testClient.get().uri("/status/404")
|
||||
.header("Host", "www.circuitbreakerstatuscode.org").exchange()
|
||||
.expectStatus().isOk().expectBody()
|
||||
.json("{\"from\":\"statusCodeFallbackController\"}");
|
||||
|
||||
testClient.get().uri("/status/200")
|
||||
.header("Host", "www.circuitbreakerstatuscode.org").exchange()
|
||||
.expectStatus().isOk().expectHeader()
|
||||
.valueEquals(ROUTE_ID_HEADER, "circuitbreaker_fallback_test_statuscode");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
@@ -69,6 +70,11 @@ public class SpringCloudCircuitBreakerTestConfig {
|
||||
return Collections.singletonMap("from", "circuitbreakerfallbackcontroller3");
|
||||
}
|
||||
|
||||
@RequestMapping("/statusCodeFallbackController")
|
||||
public Map<String, String> statusCodeFallbackController(ServerWebExchange exchange) {
|
||||
return Collections.singletonMap("from", "statusCodeFallbackController");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator circuitBreakerRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes().route("circuitbreaker_fallback_forward",
|
||||
|
||||
@@ -94,6 +94,20 @@ spring:
|
||||
name: fallbackcmd
|
||||
fallbackUri: forward:/circuitbreakerFallbackController
|
||||
|
||||
# =====================================
|
||||
- id: circuitbreaker_fallback_test_statuscode
|
||||
uri: ${test.uri}
|
||||
predicates:
|
||||
- Host=**.circuitbreakerstatuscode.org
|
||||
filters:
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: fallbackcmd
|
||||
statusCodes:
|
||||
- 500
|
||||
- "NOT_FOUND"
|
||||
fallbackUri: forward:/statusCodeFallbackController
|
||||
|
||||
# =====================================
|
||||
- id: change_uri_test
|
||||
uri: ${test.uri}
|
||||
|
||||
Reference in New Issue
Block a user