From 8bc2081842e1a18b9ded7f529c08f49675aa3d6f Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Sat, 20 Jul 2024 02:02:16 +0800 Subject: [PATCH 01/12] Fix potential memory leak when using ReadBodyRoutePredicateFactory --- .../config/GatewayAutoConfiguration.java | 7 ++++ .../filter/RemoveCachedBodyFilter.java | 24 +---------- .../RemoveCachedBodyWebExceptionHandler.java | 42 +++++++++++++++++++ .../support/ServerWebExchangeUtils.java | 22 ++++++++++ 4 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index cd5a21c059..4b0873b993 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -126,6 +126,7 @@ import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.handler.FilteringWebHandler; +import org.springframework.cloud.gateway.handler.RemoveCachedBodyWebExceptionHandler; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; @@ -276,6 +277,12 @@ public FilteringWebHandler filteringWebHandler(List globalFilters) return new FilteringWebHandler(globalFilters); } + @Bean + @ConditionalOnMissingBean + public RemoveCachedBodyWebExceptionHandler removeCachedBodyWebExceptionHandler() { + return new RemoveCachedBodyWebExceptionHandler(); + } + @Bean public GlobalCorsProperties globalCorsProperties() { return new GlobalCorsProperties(); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java index 5307cb9875..1432374eaf 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java @@ -16,37 +16,17 @@ package org.springframework.cloud.gateway.filter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.web.server.ServerWebExchange; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; - public class RemoveCachedBodyFilter implements GlobalFilter, Ordered { - private static final Log log = LogFactory.getLog(RemoveCachedBodyFilter.class); - @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).doFinally(s -> { - Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); - if (attribute != null && attribute instanceof PooledDataBuffer) { - PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; - if (dataBuffer.isAllocated()) { - if (log.isTraceEnabled()) { - log.trace("releasing cached body in exchange attribute"); - } - // ensure proper release - while (!dataBuffer.release()) { - // release() counts down until zero, will never be infinite loop - } - } - } - }); + return chain.filter(exchange).doFinally(s -> ServerWebExchangeUtils.clearRequestRequestBody(exchange)); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java new file mode 100644 index 0000000000..97db0ed5b1 --- /dev/null +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2020 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.handler; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.core.Ordered; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebExceptionHandler; + +/** + * @author Nan Chiu + */ +public class RemoveCachedBodyWebExceptionHandler implements WebExceptionHandler, Ordered { + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + ServerWebExchangeUtils.clearRequestRequestBody(exchange); + return Mono.error(ex); + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } + +} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 64118914d2..232b79aaba 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -40,6 +40,7 @@ import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBuffer; import org.springframework.core.io.buffer.NettyDataBuffer; +import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -377,6 +378,27 @@ private static Mono cacheRequestBody(ServerWebExchange exchange, boolean .switchIfEmpty(Mono.just(exchange.getRequest())).flatMap(function); } + /** + * clear the request body in a ServerWebExchange attribute. The attribute is + * {@link #CACHED_REQUEST_BODY_ATTR}. + * @param exchange the available ServerWebExchange. + */ + public static void clearRequestRequestBody(ServerWebExchange exchange) { + Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); + if (attribute != null && attribute instanceof PooledDataBuffer) { + PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; + if (dataBuffer.isAllocated()) { + if (log.isTraceEnabled()) { + log.trace("releasing cached body in exchange attribute"); + } + // ensure proper release + while (!dataBuffer.release()) { + // release() counts down until zero, will never be infinite loop + } + } + } + } + private static ServerHttpRequest decorate(ServerWebExchange exchange, DataBuffer dataBuffer, boolean cacheDecoratedRequest) { if (dataBuffer.readableByteCount() > 0) { From 34f749ad04534f3cd6bf4599ad00a09968c80a67 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Sat, 20 Jul 2024 02:32:49 +0800 Subject: [PATCH 02/12] corrected function name clearRequestRequestBody to clearCachedRequestBody in ServerWebExchangeUtils --- .../cloud/gateway/filter/RemoveCachedBodyFilter.java | 2 +- .../gateway/handler/RemoveCachedBodyWebExceptionHandler.java | 2 +- .../cloud/gateway/support/ServerWebExchangeUtils.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java index 1432374eaf..e3b15fe8b7 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java @@ -26,7 +26,7 @@ public class RemoveCachedBodyFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).doFinally(s -> ServerWebExchangeUtils.clearRequestRequestBody(exchange)); + return chain.filter(exchange).doFinally(s -> ServerWebExchangeUtils.clearCachedRequestBody(exchange)); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java index 97db0ed5b1..3f29fb898b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java @@ -30,7 +30,7 @@ public class RemoveCachedBodyWebExceptionHandler implements WebExceptionHandler, @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { - ServerWebExchangeUtils.clearRequestRequestBody(exchange); + ServerWebExchangeUtils.clearCachedRequestBody(exchange); return Mono.error(ex); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 232b79aaba..3e3cf9dade 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -383,7 +383,7 @@ private static Mono cacheRequestBody(ServerWebExchange exchange, boolean * {@link #CACHED_REQUEST_BODY_ATTR}. * @param exchange the available ServerWebExchange. */ - public static void clearRequestRequestBody(ServerWebExchange exchange) { + public static void clearCachedRequestBody(ServerWebExchange exchange) { Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); if (attribute != null && attribute instanceof PooledDataBuffer) { PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; From cfa3481867836d55b053774cc5186af205729ec9 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:59:50 +0800 Subject: [PATCH 03/12] Update Copyright year. --- .../gateway/handler/RemoveCachedBodyWebExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java index 3f29fb898b..748564c5f4 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2024 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. From b439117ec963a559fe3c4e3b2c8767e460a548e4 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:17:41 +0800 Subject: [PATCH 04/12] Clear the cache body when the route is not found. --- .../config/GatewayAutoConfiguration.java | 7 ---- .../RemoveCachedBodyWebExceptionHandler.java | 42 ------------------- .../handler/RoutePredicateHandlerMapping.java | 2 + 3 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 4b0873b993..cd5a21c059 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -126,7 +126,6 @@ import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.handler.FilteringWebHandler; -import org.springframework.cloud.gateway.handler.RemoveCachedBodyWebExceptionHandler; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; @@ -277,12 +276,6 @@ public FilteringWebHandler filteringWebHandler(List globalFilters) return new FilteringWebHandler(globalFilters); } - @Bean - @ConditionalOnMissingBean - public RemoveCachedBodyWebExceptionHandler removeCachedBodyWebExceptionHandler() { - return new RemoveCachedBodyWebExceptionHandler(); - } - @Bean public GlobalCorsProperties globalCorsProperties() { return new GlobalCorsProperties(); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java deleted file mode 100644 index 748564c5f4..0000000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RemoveCachedBodyWebExceptionHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2024 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.handler; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebExceptionHandler; - -/** - * @author Nan Chiu - */ -public class RemoveCachedBodyWebExceptionHandler implements WebExceptionHandler, Ordered { - - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - ServerWebExchangeUtils.clearCachedRequestBody(exchange); - return Mono.error(ex); - } - - @Override - public int getOrder() { - return HIGHEST_PRECEDENCE; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java index 3e81832176..e629ac268a 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java @@ -24,6 +24,7 @@ import org.springframework.cloud.gateway.config.GlobalCorsProperties; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.env.Environment; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.handler.AbstractHandlerMapping; @@ -98,6 +99,7 @@ protected Mono getHandlerInternal(ServerWebExchange exchange) { return webHandler; }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + ServerWebExchangeUtils.clearCachedRequestBody(exchange); if (logger.isTraceEnabled()) { logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); } From c2cb50ceb6f88570c1ba861347c5a48b4132fc64 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:29:24 +0800 Subject: [PATCH 05/12] Resolved merge conflict by accepting changes from main branch. --- .../handler/RoutePredicateHandlerMapping.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java index ef27050040..eb0006a8f9 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java @@ -88,23 +88,23 @@ protected Mono getHandlerInternal(ServerWebExchange exchange) { return Mono.deferContextual(contextView -> { exchange.getAttributes().put(GATEWAY_REACTOR_CONTEXT_ATTR, contextView); return lookupRoute(exchange) - // .log("route-predicate-handler-mapping", Level.FINER) //name this - .map((Function) r -> { - exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); - if (logger.isDebugEnabled()) { - logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r); - } - - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); - return webHandler; - }) - .switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { - exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); - ServerWebExchangeUtils.clearCachedRequestBody(exchange); - if (logger.isTraceEnabled()) { - logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); - } - }))); + // .log("route-predicate-handler-mapping", Level.FINER) //name this + .map((Function) r -> { + exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + if (logger.isDebugEnabled()) { + logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r); + } + + exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); + return webHandler; + }) + .switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { + exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + ServerWebExchangeUtils.clearCachedRequestBody(exchange); + if (logger.isTraceEnabled()) { + logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); + } + }))); }); } From 13e5c98d1a11011b20cb890b6e868c6105e101b1 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:21:12 +0800 Subject: [PATCH 06/12] Fix the ClassCastException when CacheRequestBodyFilter is used with CircuitBreakerFilter. --- .../CacheRequestBodyGatewayFilterFactory.java | 37 ++----------------- .../ReadBodyRoutePredicateFactory.java | 20 ++-------- .../support/ServerWebExchangeUtils.java | 32 ++++++++++++++++ ...eRequestBodyGatewayFilterFactoryTests.java | 32 ++++++++++++++-- 4 files changed, 69 insertions(+), 52 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java index 7943494e5c..7cdae94612 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java @@ -24,13 +24,9 @@ import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.Assert; import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; @@ -70,36 +66,11 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } - Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR); - if (cachedBody != null) { - return chain.filter(exchange); - } - - return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> { - final ServerRequest serverRequest = ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders); - return serverRequest.bodyToMono((config.getBodyClass())).doOnNext(objectValue -> { - Object previousCachedBody = exchange.getAttributes() - .put(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR, objectValue); - if (previousCachedBody != null) { - // store previous cached body - exchange.getAttributes().put(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR, previousCachedBody); - } - }).then(Mono.defer(() -> { - ServerHttpRequest cachedRequest = exchange - .getAttribute(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - Assert.notNull(cachedRequest, "cache request shouldn't be null"); - exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - return chain.filter(exchange.mutate().request(cachedRequest).build()).doFinally(s -> { - // - Object backupCachedBody = exchange.getAttributes() - .get(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR); - if (backupCachedBody instanceof DataBuffer dataBuffer) { - DataBufferUtils.release(dataBuffer); - } + return ServerWebExchangeUtils.cacheRequestBodyObject(exchange, config.getBodyClass(), messageReaders, + (serverHttpRequest, cachedBody) -> { + exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); + return chain.filter(exchange.mutate().request(serverHttpRequest).build()); }); - })); - }); } @Override 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..1fe3a0a569 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 @@ -29,7 +29,6 @@ import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.http.codec.HttpMessageReader; import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; /** @@ -43,8 +42,6 @@ public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute"; - private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; - private final List> messageReaders; public ReadBodyRoutePredicateFactory() { @@ -63,10 +60,7 @@ 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; + Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY); // 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 @@ -87,15 +81,9 @@ public Publisher apply(ServerWebExchange exchange) { } return Mono.just(false); } - else { - return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, - (serverHttpRequest) -> ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders) - .bodyToMono(inClass) - .doOnNext(objectValue -> exchange.getAttributes() - .put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) - .map(objectValue -> config.getPredicate().test(objectValue))); - } + + return ServerWebExchangeUtils.cacheRequestBodyObject(exchange, config.getInClass(), messageReaders, + (serverHttpRequest, bodyObject) -> Mono.just(config.predicate.test(bodyObject))); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 36ee17d934..974ec33132 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -20,14 +20,18 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import io.netty.buffer.Unpooled; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.web.reactive.function.server.ServerRequest; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -173,6 +177,12 @@ public final class ServerWebExchangeUtils { */ public static final String CACHED_REQUEST_BODY_ATTR = "cachedRequestBody"; + /** + * Cached request decoded body object key. Used when + * {@link #cacheRequestBodyObject(ServerWebExchange, Class, List, BiFunction)} + */ + public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; + /** * Gateway LoadBalancer {@link Response} attribute name. */ @@ -315,6 +325,28 @@ public static Map getUriTemplateVariables(ServerWebExchange exch return exchange.getAttributeOrDefault(URI_TEMPLATE_VARIABLES_ATTRIBUTE, new HashMap<>()); } + /** + * Caches the request body, the decoded body object and the created {@link ServerHttpRequestDecorator} in + * ServerWebExchange attributes. Those attributes are + * {@link #CACHE_REQUEST_BODY_OBJECT_KEY} and + * {@link #CACHED_REQUEST_BODY_ATTR} and + * {@link #CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR} respectively. + * @param exchange the available ServerWebExchange. + * @param bodyClass the class of the body to be decoded + * @param messageReaders the list of message readers for decoding the body. + * @param function a function to apply on the decoded body and request. + * @param the class type of the decoded body. + * @param generic type for the return {@link Mono}. + * @return Mono of type T created by the function parameter. + */ + public static Mono cacheRequestBodyObject(ServerWebExchange exchange, Class bodyClass, + List> messageReaders, BiFunction> function) { + return cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest + .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(bodyClass) + .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) + .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody))); + } + /** * Caches the request body and the created {@link ServerHttpRequestDecorator} in * ServerWebExchange attributes. Those attributes are diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java index cb4c508c8d..7bbe01a100 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.filter.factory; +import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.Test; @@ -118,6 +119,19 @@ public void cacheRequestBodyExists() { .isOk(); } + @Test + public void cacheRequestBodyWithCircuitBreaker() { + testClient.post().uri("/post").header("Host", "www.cacherequestbodywithcircuitbreaker.org") + .bodyValue(BODY_VALUE).exchange().expectStatus().isOk().expectBody(Map.class) + .consumeWith(result -> { + Map response = result.getResponseBody(); + assertThat(response).isNotNull(); + + String responseBody = (String) response.get("data"); + assertThat(responseBody).isEqualTo(BODY_VALUE); + }); + } + @Test public void toStringFormat() { CacheRequestBodyGatewayFilterFactory.Config config = new CacheRequestBodyGatewayFilterFactory.Config(); @@ -163,7 +177,19 @@ public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { .cacheRequestBody(String.class) .filter(new AssertCachedRequestBodyGatewayFilter(BODY_CACHED_EXISTS))) .uri(uri)) - .build(); + .route("cache_request_body_with_circuitbreaker_test", + r -> r.path("/post") + .and() + .host("**.cacherequestbodywithcircuitbreaker.org") + .filters(f -> f.setHostHeader("www.cacherequestbody.org") + .prefixPath("/httpbin") + .cacheRequestBody(String.class) + .filter(new AssertCachedRequestBodyGatewayFilter(BODY_VALUE)) + .filter(new CheckCachedRequestBodyReleasedGatewayFilter()) + .circuitBreaker(config -> config.setStatusCodes(Collections.singleton("200")) + .setFallbackUri("/post"))) + .uri(uri)) + .build(); } } @@ -181,7 +207,7 @@ private static class AssertCachedRequestBodyGatewayFilter implements GatewayFilt @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String body = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR); + String body = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY); if (exceptNullBody) { assertThat(body).isNull(); } @@ -203,7 +229,7 @@ private static class SetExchangeCachedRequestBodyGatewayFilter implements Gatewa @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - exchange.getAttributes().put(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR, bodyToSetCache); + exchange.getAttributes().put(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY, bodyToSetCache); return chain.filter(exchange); } From a283db2939cdec0cde8db6c4e7c59cf2412aaa10 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:32:03 +0800 Subject: [PATCH 07/12] Apply formatting fixes. --- .../CacheRequestBodyGatewayFilterFactory.java | 8 ++-- .../ReadBodyRoutePredicateFactory.java | 2 +- .../support/ServerWebExchangeUtils.java | 6 +-- ...eRequestBodyGatewayFilterFactoryTests.java | 38 +++++++++---------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java index 7cdae94612..d47f77028d 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java @@ -67,10 +67,10 @@ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { } return ServerWebExchangeUtils.cacheRequestBodyObject(exchange, config.getBodyClass(), messageReaders, - (serverHttpRequest, cachedBody) -> { - exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - return chain.filter(exchange.mutate().request(serverHttpRequest).build()); - }); + (serverHttpRequest, cachedBody) -> { + exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); + return chain.filter(exchange.mutate().request(serverHttpRequest).build()); + }); } @Override 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 1fe3a0a569..a17669ab1a 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 @@ -83,7 +83,7 @@ public Publisher apply(ServerWebExchange exchange) { } return ServerWebExchangeUtils.cacheRequestBodyObject(exchange, config.getInClass(), messageReaders, - (serverHttpRequest, bodyObject) -> Mono.just(config.predicate.test(bodyObject))); + (serverHttpRequest, bodyObject) -> Mono.just(config.predicate.test(bodyObject))); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 974ec33132..3b55b50a2b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -342,9 +342,9 @@ public static Map getUriTemplateVariables(ServerWebExchange exch public static Mono cacheRequestBodyObject(ServerWebExchange exchange, Class bodyClass, List> messageReaders, BiFunction> function) { return cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(bodyClass) - .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) - .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody))); + .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(bodyClass) + .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) + .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody))); } /** diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java index 7bbe01a100..1c17d167c9 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java @@ -122,14 +122,14 @@ public void cacheRequestBodyExists() { @Test public void cacheRequestBodyWithCircuitBreaker() { testClient.post().uri("/post").header("Host", "www.cacherequestbodywithcircuitbreaker.org") - .bodyValue(BODY_VALUE).exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - String responseBody = (String) response.get("data"); - assertThat(responseBody).isEqualTo(BODY_VALUE); - }); + .bodyValue(BODY_VALUE).exchange().expectStatus().isOk().expectBody(Map.class) + .consumeWith(result -> { + Map response = result.getResponseBody(); + assertThat(response).isNotNull(); + + String responseBody = (String) response.get("data"); + assertThat(responseBody).isEqualTo(BODY_VALUE); + }); } @Test @@ -179,17 +179,17 @@ public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { .uri(uri)) .route("cache_request_body_with_circuitbreaker_test", r -> r.path("/post") - .and() - .host("**.cacherequestbodywithcircuitbreaker.org") - .filters(f -> f.setHostHeader("www.cacherequestbody.org") - .prefixPath("/httpbin") - .cacheRequestBody(String.class) - .filter(new AssertCachedRequestBodyGatewayFilter(BODY_VALUE)) - .filter(new CheckCachedRequestBodyReleasedGatewayFilter()) - .circuitBreaker(config -> config.setStatusCodes(Collections.singleton("200")) - .setFallbackUri("/post"))) - .uri(uri)) - .build(); + .and() + .host("**.cacherequestbodywithcircuitbreaker.org") + .filters(f -> f.setHostHeader("www.cacherequestbody.org") + .prefixPath("/httpbin") + .cacheRequestBody(String.class) + .filter(new AssertCachedRequestBodyGatewayFilter(BODY_VALUE)) + .filter(new CheckCachedRequestBodyReleasedGatewayFilter()) + .circuitBreaker(config -> config.setStatusCodes(Collections.singleton("200")) + .setFallbackUri("/post"))) + .uri(uri)) + .build(); } } From c0fc92387a30039029f1293e0779ba6f59440c38 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:35:31 +0800 Subject: [PATCH 08/12] Apply formatting fixes --- .../cloud/gateway/support/ServerWebExchangeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 3b55b50a2b..df7e027f54 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -328,8 +328,8 @@ public static Map getUriTemplateVariables(ServerWebExchange exch /** * Caches the request body, the decoded body object and the created {@link ServerHttpRequestDecorator} in * ServerWebExchange attributes. Those attributes are - * {@link #CACHE_REQUEST_BODY_OBJECT_KEY} and * {@link #CACHED_REQUEST_BODY_ATTR} and + * {@link #CACHE_REQUEST_BODY_OBJECT_KEY} and * {@link #CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR} respectively. * @param exchange the available ServerWebExchange. * @param bodyClass the class of the body to be decoded From 463badabe92741dccf6776c2ae8470b24b806216 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:44:45 +0800 Subject: [PATCH 09/12] Apply formatting fixes --- .../cloud/gateway/support/ServerWebExchangeUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index df7e027f54..ce270192a9 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -30,8 +30,6 @@ import io.netty.buffer.Unpooled; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.web.reactive.function.server.ServerRequest; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -46,12 +44,14 @@ import org.springframework.core.io.buffer.NettyDataBuffer; import org.springframework.core.io.buffer.PooledDataBuffer; import org.springframework.http.HttpStatus; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.reactive.DispatcherHandler; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; From dbec7d9e4ae4338006b04b2f753316e84214327f Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 04:08:34 +0800 Subject: [PATCH 10/12] Compatibility for requests with empty bodies. --- .../handler/predicate/ReadBodyRoutePredicateFactory.java | 7 ++++++- .../cloud/gateway/support/ServerWebExchangeUtils.java | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) 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 a17669ab1a..1ac28ff65d 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 @@ -83,7 +83,12 @@ public Publisher apply(ServerWebExchange exchange) { } return ServerWebExchangeUtils.cacheRequestBodyObject(exchange, config.getInClass(), messageReaders, - (serverHttpRequest, bodyObject) -> Mono.just(config.predicate.test(bodyObject))); + (serverHttpRequest, bodyObject) -> { + if (bodyObject == null) { + return Mono.just(false); + } + return Mono.just(config.predicate.test(bodyObject)); + }); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index ce270192a9..0ebbe903c1 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -344,7 +344,8 @@ public static Mono cacheRequestBodyObject(ServerWebExchange exchange, return cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(bodyClass) .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) - .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody))); + .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody)) + .switchIfEmpty(function.apply(serverHttpRequest, null))); } /** From 49ae24fe0f303a679e50137e09255767c86a0c32 Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:49:01 +0800 Subject: [PATCH 11/12] update CacheRequestBodyFilter documentation. --- .../gatewayfilter-factories/cacherequestbody-factory.adoc | 4 ++-- .../handler/predicate/ReadBodyRoutePredicateFactory.java | 2 +- .../cloud/gateway/support/ServerWebExchangeUtils.java | 6 +++--- .../factory/CacheRequestBodyGatewayFilterFactoryTests.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/cacherequestbody-factory.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/cacherequestbody-factory.adoc index 513c99d6e0..b7b44332f0 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/cacherequestbody-factory.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/cacherequestbody-factory.adoc @@ -14,7 +14,7 @@ public RouteLocator routes(RouteLocatorBuilder builder) { .route("cache_request_body_route", r -> r.path("/downstream/**") .filters(f -> f.prefixPath("/httpbin") .cacheRequestBody(String.class).uri(uri)) - .build(); + .build()); } ---- @@ -36,7 +36,7 @@ spring: bodyClass: java.lang.String ---- `CacheRequestBody` extracts the request body and converts it to a body class (such as `java.lang.String`, defined in the preceding example). -`CacheRequestBody` then places it in the attributes available from `ServerWebExchange.getAttributes()`, with a key defined in `ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR`. +`CacheRequestBody` then places it in the attributes available from `ServerWebExchange.getAttributes()`, with a key defined in `ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_ATTR`. NOTE: This filter works only with HTTP (including HTTPS) requests. 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 1ac28ff65d..e598f19239 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 @@ -60,7 +60,7 @@ public AsyncPredicate applyAsync(Config config) { return new AsyncPredicate() { @Override public Publisher apply(ServerWebExchange exchange) { - Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY); + Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_ATTR); // 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 diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index 0ebbe903c1..086621125f 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -181,7 +181,7 @@ public final class ServerWebExchangeUtils { * Cached request decoded body object key. Used when * {@link #cacheRequestBodyObject(ServerWebExchange, Class, List, BiFunction)} */ - public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; + public static final String CACHE_REQUEST_BODY_OBJECT_ATTR = "cachedRequestBodyObject"; /** * Gateway LoadBalancer {@link Response} attribute name. @@ -329,7 +329,7 @@ public static Map getUriTemplateVariables(ServerWebExchange exch * Caches the request body, the decoded body object and the created {@link ServerHttpRequestDecorator} in * ServerWebExchange attributes. Those attributes are * {@link #CACHED_REQUEST_BODY_ATTR} and - * {@link #CACHE_REQUEST_BODY_OBJECT_KEY} and + * {@link #CACHE_REQUEST_BODY_OBJECT_ATTR} and * {@link #CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR} respectively. * @param exchange the available ServerWebExchange. * @param bodyClass the class of the body to be decoded @@ -343,7 +343,7 @@ public static Mono cacheRequestBodyObject(ServerWebExchange exchange, List> messageReaders, BiFunction> function) { return cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> ServerRequest .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(bodyClass) - .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) + .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_ATTR, objectValue)) .flatMap(cachedBody -> function.apply(serverHttpRequest, cachedBody)) .switchIfEmpty(function.apply(serverHttpRequest, null))); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java index 1c17d167c9..c71ab86f5c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java @@ -207,7 +207,7 @@ private static class AssertCachedRequestBodyGatewayFilter implements GatewayFilt @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String body = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY); + String body = exchange.getAttribute(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_ATTR); if (exceptNullBody) { assertThat(body).isNull(); } @@ -229,7 +229,7 @@ private static class SetExchangeCachedRequestBodyGatewayFilter implements Gatewa @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - exchange.getAttributes().put(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_KEY, bodyToSetCache); + exchange.getAttributes().put(ServerWebExchangeUtils.CACHE_REQUEST_BODY_OBJECT_ATTR, bodyToSetCache); return chain.filter(exchange); } From bddcb4e10feae7d1d973da87bbd2fb12ab363d8f Mon Sep 17 00:00:00 2001 From: qnnn <65326092+qnnn@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:38:30 +0800 Subject: [PATCH 12/12] fix checkstyle --- .../cloud/gateway/support/ServerWebExchangeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index a458d18c5b..3cb7017d21 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -20,8 +20,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; -import java.util.Locale; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.BiFunction;