diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java index 5dbb3c7d20..6db6974028 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java @@ -38,6 +38,7 @@ import org.springframework.cloud.gateway.server.mvc.common.Shortcut; import org.springframework.cloud.gateway.server.mvc.common.Shortcut.Type; import org.springframework.cloud.gateway.server.mvc.common.WeightConfig; +import org.springframework.cloud.gateway.server.mvc.filter.BodyFilterFunctions; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -59,7 +60,6 @@ import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_ROUTE_ID_ATTR; import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.WEIGHT_ATTR; import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.cacheAndReadBody; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getAttribute; import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.putAttribute; public abstract class GatewayRequestPredicates { @@ -487,22 +487,10 @@ private static final class ReadBodyPredicate implements RequestPredicate { @Override @SuppressWarnings("unchecked") public boolean test(ServerRequest request) { - try { - Object cachedBody = getAttribute(request, READ_BODY_CACHE_OBJECT_KEY); + ServerRequest cachedRequest = BodyFilterFunctions.adaptCachedBody().apply(request); - if (cachedBody != null) { - return predicate.test((T) cachedBody); - } - } - catch (ClassCastException e) { - if (log.isDebugEnabled()) { - log.debug("Predicate test failed because class in predicate " - + "does not match the cached body object", e); - } - } - - return cacheAndReadBody(request, toRead).map(body -> { - putAttribute(request, READ_BODY_CACHE_OBJECT_KEY, body); + return cacheAndReadBody(cachedRequest, toRead).map(body -> { + putAttribute(cachedRequest, READ_BODY_CACHE_OBJECT_KEY, body); return predicate.test(body); }).orElse(false); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java index 4226ecbc2e..25ebeabda1 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java @@ -23,11 +23,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; @@ -63,38 +63,33 @@ public AsyncPredicate applyAsync(Config config) { return new AsyncPredicate() { @Override public Publisher apply(ServerWebExchange exchange) { - Class inClass = config.getInClass(); - - Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); - Mono modifiedBody; - // We can only read the body from the request once, once that happens if - // we try to read the body again an exception will be thrown. The below - // if/else caches the body object as a request attribute in the - // ServerWebExchange so if this filter is run more than once (due to more - // than one route using it) we do not try to read the request body - // multiple times - if (cachedBody != null) { - try { - boolean test = config.predicate.test(cachedBody); - exchange.getAttributes().put(TEST_ATTRIBUTE, test); - return Mono.just(test); - } - catch (ClassCastException e) { - if (log.isDebugEnabled()) { - log.debug("Predicate test failed because class in predicate " - + "does not match the cached body object", e); - } - } - return Mono.just(false); + exchange.getAttributes().put(TEST_ATTRIBUTE, false); + + ServerHttpRequest mutableRequest = exchange + .getAttribute(ServerWebExchangeUtils.CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); + if (mutableRequest != null) { + return ServerRequest.create(exchange.mutate().request(mutableRequest).build(), messageReaders) + .bodyToMono(config.getInClass()) + .doOnNext(objectValue -> exchange.getAttributes() + .put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) + .map(objectValue -> { + boolean test = config.getPredicate().test(objectValue); + exchange.getAttributes().put(TEST_ATTRIBUTE, test); + return test; + }); } else { return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders) - .bodyToMono(inClass) + .bodyToMono(config.getInClass()) .doOnNext(objectValue -> exchange.getAttributes() .put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) - .map(objectValue -> config.getPredicate().test(objectValue))); + .map(objectValue -> { + boolean test = config.getPredicate().test(objectValue); + exchange.getAttributes().put(TEST_ATTRIBUTE, test); + return test; + })); } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java index 4b5c2b8df7..4037b801d7 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java @@ -85,6 +85,23 @@ public void readBodyWorks() { } + @Test + public void multipleReadBodyMatchingTest() { + + Event messageChannelEvent = new Event("message.channels", "bar"); + + webClient.post() + .uri("/events") + .body(BodyInserters.fromValue(messageChannelEvent)) + .header("repeatable") + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("$.headers.World") + .isEqualTo("Hello"); + } + @Test public void toStringFormat() { Config config = new Config(); @@ -118,6 +135,25 @@ public RouteLocator routeLocator(RouteLocatorBuilder builder) { .readBody(Event.class, eventPredicate("message")) .filters(f -> f.setPath("/message/events")) .uri("lb://message")) + .route(p -> p.order(-100) + .path("/events") + .and() + .method(HttpMethod.POST) + .and() + .readBody(String.class, string -> false) + .and() + .header("repeatable") + .uri("lb://message")) + .route(p -> p.order(-99) + .path("/events") + .and() + .method(HttpMethod.POST) + .and() + .readBody(Event.class, eventPredicate("message")) + .and() + .header("repeatable") + .filters(f -> f.setPath("/message/events")) + .uri("lb://message")) .build(); }