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:
Arjen Poutsma
2018-09-17 14:49:40 +02:00
parent 51f7a3e40f
commit 88ab911cf1
3 changed files with 98 additions and 63 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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, "");
}
}
}