Provide matched pattern information in WebFlux fn
This commit stores the first matching path pattern in the attribute `RouterFunctions.MATCHING_PATTERN_ATTRIBUTE`. Issue: SPR-17098
This commit is contained in:
@@ -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<String, String> mergePathVariables(Map<String, String> oldVariables,
|
||||
Map<String, String> newVariables) {
|
||||
|
||||
if (!newVariables.isEmpty()) {
|
||||
Map<String, String> 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<String, String> variables,
|
||||
String pattern) {
|
||||
Map<String, String> 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<ServerRequest> nest(ServerRequest request) {
|
||||
return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer()))
|
||||
.map(info -> new SubPathServerRequestWrapper(request, info));
|
||||
}
|
||||
|
||||
private void mergeTemplateVariables(ServerRequest request, Map<String, String> variables) {
|
||||
if (!variables.isEmpty()) {
|
||||
Map<String, String> oldVariables = request.pathVariables();
|
||||
Map<String, String> 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<String, String> pathVariables;
|
||||
private final Map<String, Object> 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<String, String> mergePathVariables(ServerRequest request,
|
||||
Map<String, String> pathVariables) {
|
||||
private static Map<String, Object> mergeAttributes(ServerRequest request,
|
||||
Map<String, String> pathVariables, String pattern) {
|
||||
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
|
||||
|
||||
Map<String, String> 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<Object> attribute(String name) {
|
||||
return this.request.attribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> attributes() {
|
||||
return this.request.attributes();
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -721,8 +751,11 @@ public abstract class RequestPredicates {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, String> pathVariables() {
|
||||
return this.pathVariables;
|
||||
return (Map<String, String>) this.attributes.getOrDefault(
|
||||
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<ServerResponse> 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<String, String> variables) {
|
||||
if (!variables.isEmpty()) {
|
||||
Map<String, Object> attributes = request.attributes();
|
||||
Map<String, String> oldVariables =
|
||||
(Map<String, String>) request.attribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
|
||||
.orElseGet(LinkedHashMap::new);
|
||||
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
|
||||
mergedVariables.putAll(variables);
|
||||
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
|
||||
Collections.unmodifiableMap(mergedVariables));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Visitor visitor) {
|
||||
|
||||
@@ -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<ServerResponse> bar(ServerRequest request) {
|
||||
return ServerResponse.ok().syncBody("bar");
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> baz(ServerRequest request) {
|
||||
return ServerResponse.ok().syncBody("baz");
|
||||
public Mono<ServerResponse> 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<String> responseBody = Mono.just(pathVariables.toString());
|
||||
String pattern = matchingPattern(request);
|
||||
Flux<String> 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, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user