Browse Source

Merge remote-tracking branch 'origin/2.2.x'

pull/1856/head
Ryan Baxter 4 years ago
parent
commit
1cb8b10c05
  1. 49
      docs/src/main/asciidoc/spring-cloud-gateway.adoc
  2. 16
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
  3. 44
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java
  4. 11
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java
  5. 18
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java
  6. 6
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java
  7. 14
      spring-cloud-gateway-core/src/test/resources/application.yml

49
docs/src/main/asciidoc/spring-cloud-gateway.adoc

@ -723,6 +723,55 @@ It is added to the `ServerWebExchange` as the `ServerWebExchangeUtils.CIRCUITBRE @@ -723,6 +723,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

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

@ -20,9 +20,7 @@ import java.io.IOException; @@ -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; @@ -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; @@ -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 @@ -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);
}
public GatewayFilter apply(String routeId, Repeat<ServerWebExchange> repeat,

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

@ -17,7 +17,10 @@ @@ -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 @@ -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 @@ -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
@ -80,12 +87,23 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends @@ -80,12 +87,23 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends
@Override
public GatewayFilter apply(Config config) {
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId());
Set<HttpStatus> statuses = config.getStatusCodes().stream()
.map(HttpStatusHolder::parse)
.filter(statusHolder -> statusHolder.getHttpStatus() != null)
.map(HttpStatusHolder::getHttpStatus).collect(Collectors.toSet());
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return cb.run(chain.filter(exchange), t -> {
return cb.run(chain.filter(exchange).doOnSuccess(v -> {
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 +159,8 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends @@ -141,6 +159,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 +199,28 @@ public abstract class SpringCloudCircuitBreakerFilterFactory extends @@ -179,6 +199,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);
}
}
}

11
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java

@ -17,9 +17,11 @@ @@ -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;
@ -187,6 +189,15 @@ public final class ServerWebExchangeUtils { @@ -187,6 +189,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()) {

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

@ -104,4 +104,22 @@ public abstract class SpringCloudCircuitBreakerFilterFactoryTests @@ -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");
}
}

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

@ -37,6 +37,7 @@ import org.springframework.web.bind.annotation.RestController; @@ -37,6 +37,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;
@ -70,6 +71,11 @@ public class SpringCloudCircuitBreakerTestConfig { @@ -70,6 +71,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",

14
spring-cloud-gateway-core/src/test/resources/application.yml

@ -94,6 +94,20 @@ spring: @@ -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}

Loading…
Cancel
Save