diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java index 92f65245e7..cc9a625958 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; @@ -355,6 +356,31 @@ public abstract class RequestPredicates { request.attributes().putAll(attributes); } + private static Map mergePathVariables(Map oldVariables, + Map newVariables) { + + if (!newVariables.isEmpty()) { + Map mergedVariables = new LinkedHashMap<>(oldVariables); + mergedVariables.putAll(newVariables); + return mergedVariables; + } + else { + return oldVariables; + } + } + + private static String mergePatterns(@Nullable String oldPattern, String newPattern) { + if (oldPattern != null) { + if (oldPattern.endsWith("/") && newPattern.startsWith("/")) { + oldPattern = oldPattern.substring(0, oldPattern.length() - 1); + } + return oldPattern + newPattern; + } + else { + return newPattern; + } + + } private static class HttpMethodPredicate implements RequestPredicate { @@ -403,9 +429,10 @@ public abstract class RequestPredicates { public boolean test(ServerRequest request) { PathContainer pathContainer = request.pathContainer(); PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); - traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null); + String patternString = this.pattern.getPatternString(); + traceMatch("Pattern", patternString, request.path(), info != null); if (info != null) { - mergeTemplateVariables(request, info.getUriVariables()); + mergeAttributes(request, info.getUriVariables(), patternString); return true; } else { @@ -413,20 +440,22 @@ public abstract class RequestPredicates { } } + private static void mergeAttributes(ServerRequest request, Map variables, + String pattern) { + Map pathVariables = mergePathVariables(request.pathVariables(), variables); + request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, + Collections.unmodifiableMap(pathVariables)); + + pattern = mergePatterns( + (String) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), + pattern); + request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); + } + @Override public Optional nest(ServerRequest request) { return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer())) - .map(info -> new SubPathServerRequestWrapper(request, info)); - } - - private void mergeTemplateVariables(ServerRequest request, Map variables) { - if (!variables.isEmpty()) { - Map oldVariables = request.pathVariables(); - Map mergedVariables = new LinkedHashMap<>(oldVariables); - mergedVariables.putAll(variables); - request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, - Collections.unmodifiableMap(mergedVariables)); - } + .map(info -> new SubPathServerRequestWrapper(request, info, this.pattern.getPatternString())); } @Override @@ -601,23 +630,29 @@ public abstract class RequestPredicates { private final ServerRequest request; - private final PathContainer subPathContainer; + private final PathContainer pathContainer; - private final Map pathVariables; + private final Map attributes; - public SubPathServerRequestWrapper(ServerRequest request, PathPattern.PathRemainingMatchInfo info) { + public SubPathServerRequestWrapper(ServerRequest request, + PathPattern.PathRemainingMatchInfo info, String pattern) { this.request = request; - this.subPathContainer = new SubPathContainer(info.getPathRemaining()); - - this.pathVariables = mergePathVariables(request, info.getUriVariables()); + this.pathContainer = new SubPathContainer(info.getPathRemaining()); + this.attributes = mergeAttributes(request, info.getUriVariables(), pattern); } - private static Map mergePathVariables(ServerRequest request, - Map pathVariables) { + private static Map mergeAttributes(ServerRequest request, + Map pathVariables, String pattern) { + Map result = new ConcurrentHashMap<>(request.attributes()); - Map result = new LinkedHashMap<>(request.pathVariables()); - result.putAll(pathVariables); - return Collections.unmodifiableMap(result); + result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, + mergePathVariables(request.pathVariables(), pathVariables)); + + pattern = mergePatterns( + (String) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), + pattern); + result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); + return result; } @Override @@ -642,12 +677,12 @@ public abstract class RequestPredicates { @Override public String path() { - return this.subPathContainer.value(); + return this.pathContainer.value(); } @Override public PathContainer pathContainer() { - return this.subPathContainer; + return this.pathContainer; } @Override @@ -700,14 +735,9 @@ public abstract class RequestPredicates { return this.request.bodyToFlux(typeReference); } - @Override - public Optional attribute(String name) { - return this.request.attribute(name); - } - @Override public Map attributes() { - return this.request.attributes(); + return this.attributes; } @Override @@ -721,8 +751,11 @@ public abstract class RequestPredicates { } @Override + @SuppressWarnings("unchecked") public Map pathVariables() { - return this.pathVariables; + return (Map) this.attributes.getOrDefault( + RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap()); + } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index e486fc5842..e83c87e6d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -16,8 +16,6 @@ package org.springframework.web.reactive.function.server; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; @@ -71,6 +69,14 @@ public abstract class RouterFunctions { public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = RouterFunctions.class.getName() + ".uriTemplateVariables"; + /** + * Name of the {@link ServerWebExchange#getAttributes() attribute} that + * contains the matching pattern. + */ + public static final String MATCHING_PATTERN_ATTRIBUTE = + RouterFunctions.class.getName() + ".matchingPattern"; + + private static final HandlerFunction NOT_FOUND_HANDLER = request -> ServerResponse.notFound().build(); @@ -934,25 +940,13 @@ public abstract class RouterFunctions { } return this.routerFunction.route(nestedRequest) .doOnNext(match -> { - mergeTemplateVariables(serverRequest, nestedRequest.pathVariables()); + serverRequest.attributes().clear(); + serverRequest.attributes().putAll(nestedRequest.attributes()); }); } ).orElseGet(Mono::empty); } - @SuppressWarnings("unchecked") - private void mergeTemplateVariables(ServerRequest request, Map variables) { - if (!variables.isEmpty()) { - Map attributes = request.attributes(); - Map oldVariables = - (Map) request.attribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE) - .orElseGet(LinkedHashMap::new); - Map mergedVariables = new LinkedHashMap<>(oldVariables); - mergedVariables.putAll(variables); - attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, - Collections.unmodifiableMap(mergedVariables)); - } - } @Override public void accept(Visitor visitor) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/NestedRouteIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/NestedRouteIntegrationTests.java index dbdb729360..32df4d1ddc 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/NestedRouteIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/NestedRouteIntegrationTests.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.server; import java.util.Map; import org.junit.Test; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.http.HttpMethod; @@ -46,8 +47,8 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati protected RouterFunction routerFunction() { NestedHandler nestedHandler = new NestedHandler(); return nest(path("/foo/"), - route(GET("/bar"), nestedHandler::bar) - .andRoute(GET("/baz"), nestedHandler::baz)) + route(GET("/bar"), nestedHandler::pattern) + .andRoute(GET("/baz"), nestedHandler::pattern)) .andNest(GET("/{foo}"), route(GET("/bar"), nestedHandler::variables).and( nest(GET("/{bar}"), @@ -63,7 +64,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati restTemplate.getForEntity("http://localhost:" + port + "/foo/bar", String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("bar", result.getBody()); + assertEquals("/foo/bar", result.getBody()); } @Test @@ -72,7 +73,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati restTemplate.getForEntity("http://localhost:" + port + "/foo/baz", String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("baz", result.getBody()); + assertEquals("/foo/baz", result.getBody()); } @Test @@ -81,7 +82,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati restTemplate.getForEntity("http://localhost:" + port + "/1/2/3", String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("{foo=1, bar=2, baz=3}", result.getBody()); + assertEquals("/{foo}/{bar}/{baz}\n{foo=1, bar=2, baz=3}", result.getBody()); } // SPR-16868 @@ -91,7 +92,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati restTemplate.getForEntity("http://localhost:" + port + "/1/bar", String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("{foo=1}", result.getBody()); + assertEquals("/{foo}/bar\n{foo=1}", result.getBody()); } @@ -102,7 +103,7 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati restTemplate.getForEntity("http://localhost:" + port + "/qux/quux", String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals("{qux=qux}", result.getBody()); + assertEquals("/{qux}/quux\n{qux=qux}", result.getBody()); } @@ -120,12 +121,9 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati private static class NestedHandler { - public Mono bar(ServerRequest request) { - return ServerResponse.ok().syncBody("bar"); - } - - public Mono baz(ServerRequest request) { - return ServerResponse.ok().syncBody("baz"); + public Mono pattern(ServerRequest request) { + String pattern = matchingPattern(request); + return ServerResponse.ok().syncBody(pattern); } @SuppressWarnings("unchecked") @@ -136,10 +134,20 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati assertTrue( (pathVariables.equals(attributePathVariables)) || (pathVariables.isEmpty() && (attributePathVariables == null))); - Mono responseBody = Mono.just(pathVariables.toString()); + String pattern = matchingPattern(request); + Flux responseBody; + if (!pattern.isEmpty()) { + responseBody = Flux.just(pattern, "\n", pathVariables.toString()); + } else { + responseBody = Flux.just(pathVariables.toString()); + } return ServerResponse.ok().body(responseBody, String.class); } + private String matchingPattern(ServerRequest request) { + return (String) request.attributes().getOrDefault(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, ""); + } + } }