Skip to content

Commit 88ab911

Browse files
committed
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
1 parent 51f7a3e commit 88ab911

File tree

3 files changed

+98
-63
lines changed

3 files changed

+98
-63
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Map;
3131
import java.util.Optional;
3232
import java.util.Set;
33+
import java.util.concurrent.ConcurrentHashMap;
3334
import java.util.function.Function;
3435
import java.util.function.Predicate;
3536

@@ -355,6 +356,31 @@ private static void restoreAttributes(ServerRequest request, Map<String, Object>
355356
request.attributes().putAll(attributes);
356357
}
357358

359+
private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
360+
Map<String, String> newVariables) {
361+
362+
if (!newVariables.isEmpty()) {
363+
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
364+
mergedVariables.putAll(newVariables);
365+
return mergedVariables;
366+
}
367+
else {
368+
return oldVariables;
369+
}
370+
}
371+
372+
private static String mergePatterns(@Nullable String oldPattern, String newPattern) {
373+
if (oldPattern != null) {
374+
if (oldPattern.endsWith("/") && newPattern.startsWith("/")) {
375+
oldPattern = oldPattern.substring(0, oldPattern.length() - 1);
376+
}
377+
return oldPattern + newPattern;
378+
}
379+
else {
380+
return newPattern;
381+
}
382+
383+
}
358384

359385
private static class HttpMethodPredicate implements RequestPredicate {
360386

@@ -403,30 +429,33 @@ public PathPatternPredicate(PathPattern pattern) {
403429
public boolean test(ServerRequest request) {
404430
PathContainer pathContainer = request.pathContainer();
405431
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
406-
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
432+
String patternString = this.pattern.getPatternString();
433+
traceMatch("Pattern", patternString, request.path(), info != null);
407434
if (info != null) {
408-
mergeTemplateVariables(request, info.getUriVariables());
435+
mergeAttributes(request, info.getUriVariables(), patternString);
409436
return true;
410437
}
411438
else {
412439
return false;
413440
}
414441
}
415442

443+
private static void mergeAttributes(ServerRequest request, Map<String, String> variables,
444+
String pattern) {
445+
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables);
446+
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
447+
Collections.unmodifiableMap(pathVariables));
448+
449+
pattern = mergePatterns(
450+
(String) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
451+
pattern);
452+
request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
453+
}
454+
416455
@Override
417456
public Optional<ServerRequest> nest(ServerRequest request) {
418457
return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer()))
419-
.map(info -> new SubPathServerRequestWrapper(request, info));
420-
}
421-
422-
private void mergeTemplateVariables(ServerRequest request, Map<String, String> variables) {
423-
if (!variables.isEmpty()) {
424-
Map<String, String> oldVariables = request.pathVariables();
425-
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
426-
mergedVariables.putAll(variables);
427-
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
428-
Collections.unmodifiableMap(mergedVariables));
429-
}
458+
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern.getPatternString()));
430459
}
431460

432461
@Override
@@ -601,23 +630,29 @@ private static class SubPathServerRequestWrapper implements ServerRequest {
601630

602631
private final ServerRequest request;
603632

604-
private final PathContainer subPathContainer;
633+
private final PathContainer pathContainer;
605634

606-
private final Map<String, String> pathVariables;
635+
private final Map<String, Object> attributes;
607636

608-
public SubPathServerRequestWrapper(ServerRequest request, PathPattern.PathRemainingMatchInfo info) {
637+
public SubPathServerRequestWrapper(ServerRequest request,
638+
PathPattern.PathRemainingMatchInfo info, String pattern) {
609639
this.request = request;
610-
this.subPathContainer = new SubPathContainer(info.getPathRemaining());
611-
612-
this.pathVariables = mergePathVariables(request, info.getUriVariables());
640+
this.pathContainer = new SubPathContainer(info.getPathRemaining());
641+
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
613642
}
614643

615-
private static Map<String, String> mergePathVariables(ServerRequest request,
616-
Map<String, String> pathVariables) {
644+
private static Map<String, Object> mergeAttributes(ServerRequest request,
645+
Map<String, String> pathVariables, String pattern) {
646+
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
617647

618-
Map<String, String> result = new LinkedHashMap<>(request.pathVariables());
619-
result.putAll(pathVariables);
620-
return Collections.unmodifiableMap(result);
648+
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
649+
mergePathVariables(request.pathVariables(), pathVariables));
650+
651+
pattern = mergePatterns(
652+
(String) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
653+
pattern);
654+
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
655+
return result;
621656
}
622657

623658
@Override
@@ -642,12 +677,12 @@ public UriBuilder uriBuilder() {
642677

643678
@Override
644679
public String path() {
645-
return this.subPathContainer.value();
680+
return this.pathContainer.value();
646681
}
647682

648683
@Override
649684
public PathContainer pathContainer() {
650-
return this.subPathContainer;
685+
return this.pathContainer;
651686
}
652687

653688
@Override
@@ -700,14 +735,9 @@ public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
700735
return this.request.bodyToFlux(typeReference);
701736
}
702737

703-
@Override
704-
public Optional<Object> attribute(String name) {
705-
return this.request.attribute(name);
706-
}
707-
708738
@Override
709739
public Map<String, Object> attributes() {
710-
return this.request.attributes();
740+
return this.attributes;
711741
}
712742

713743
@Override
@@ -721,8 +751,11 @@ public MultiValueMap<String, String> queryParams() {
721751
}
722752

723753
@Override
754+
@SuppressWarnings("unchecked")
724755
public Map<String, String> pathVariables() {
725-
return this.pathVariables;
756+
return (Map<String, String>) this.attributes.getOrDefault(
757+
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
758+
726759
}
727760

728761
@Override

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.web.reactive.function.server;
1818

19-
import java.util.Collections;
20-
import java.util.LinkedHashMap;
2119
import java.util.List;
2220
import java.util.Map;
2321
import java.util.function.BiFunction;
@@ -71,6 +69,14 @@ public abstract class RouterFunctions {
7169
public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE =
7270
RouterFunctions.class.getName() + ".uriTemplateVariables";
7371

72+
/**
73+
* Name of the {@link ServerWebExchange#getAttributes() attribute} that
74+
* contains the matching pattern.
75+
*/
76+
public static final String MATCHING_PATTERN_ATTRIBUTE =
77+
RouterFunctions.class.getName() + ".matchingPattern";
78+
79+
7480
private static final HandlerFunction<ServerResponse> NOT_FOUND_HANDLER =
7581
request -> ServerResponse.notFound().build();
7682

@@ -934,25 +940,13 @@ public Mono<HandlerFunction<T>> route(ServerRequest serverRequest) {
934940
}
935941
return this.routerFunction.route(nestedRequest)
936942
.doOnNext(match -> {
937-
mergeTemplateVariables(serverRequest, nestedRequest.pathVariables());
943+
serverRequest.attributes().clear();
944+
serverRequest.attributes().putAll(nestedRequest.attributes());
938945
});
939946
}
940947
).orElseGet(Mono::empty);
941948
}
942949

943-
@SuppressWarnings("unchecked")
944-
private void mergeTemplateVariables(ServerRequest request, Map<String, String> variables) {
945-
if (!variables.isEmpty()) {
946-
Map<String, Object> attributes = request.attributes();
947-
Map<String, String> oldVariables =
948-
(Map<String, String>) request.attribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
949-
.orElseGet(LinkedHashMap::new);
950-
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
951-
mergedVariables.putAll(variables);
952-
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
953-
Collections.unmodifiableMap(mergedVariables));
954-
}
955-
}
956950

957951
@Override
958952
public void accept(Visitor visitor) {

spring-webflux/src/test/java/org/springframework/web/reactive/function/server/NestedRouteIntegrationTests.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Map;
2020

2121
import org.junit.Test;
22+
import reactor.core.publisher.Flux;
2223
import reactor.core.publisher.Mono;
2324

2425
import org.springframework.http.HttpMethod;
@@ -46,8 +47,8 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati
4647
protected RouterFunction<?> routerFunction() {
4748
NestedHandler nestedHandler = new NestedHandler();
4849
return nest(path("/foo/"),
49-
route(GET("/bar"), nestedHandler::bar)
50-
.andRoute(GET("/baz"), nestedHandler::baz))
50+
route(GET("/bar"), nestedHandler::pattern)
51+
.andRoute(GET("/baz"), nestedHandler::pattern))
5152
.andNest(GET("/{foo}"),
5253
route(GET("/bar"), nestedHandler::variables).and(
5354
nest(GET("/{bar}"),
@@ -63,7 +64,7 @@ public void bar() {
6364
restTemplate.getForEntity("http://localhost:" + port + "/foo/bar", String.class);
6465

6566
assertEquals(HttpStatus.OK, result.getStatusCode());
66-
assertEquals("bar", result.getBody());
67+
assertEquals("/foo/bar", result.getBody());
6768
}
6869

6970
@Test
@@ -72,7 +73,7 @@ public void baz() {
7273
restTemplate.getForEntity("http://localhost:" + port + "/foo/baz", String.class);
7374

7475
assertEquals(HttpStatus.OK, result.getStatusCode());
75-
assertEquals("baz", result.getBody());
76+
assertEquals("/foo/baz", result.getBody());
7677
}
7778

7879
@Test
@@ -81,7 +82,7 @@ public void variables() {
8182
restTemplate.getForEntity("http://localhost:" + port + "/1/2/3", String.class);
8283

8384
assertEquals(HttpStatus.OK, result.getStatusCode());
84-
assertEquals("{foo=1, bar=2, baz=3}", result.getBody());
85+
assertEquals("/{foo}/{bar}/{baz}\n{foo=1, bar=2, baz=3}", result.getBody());
8586
}
8687

8788
// SPR-16868
@@ -91,7 +92,7 @@ public void parentVariables() {
9192
restTemplate.getForEntity("http://localhost:" + port + "/1/bar", String.class);
9293

9394
assertEquals(HttpStatus.OK, result.getStatusCode());
94-
assertEquals("{foo=1}", result.getBody());
95+
assertEquals("/{foo}/bar\n{foo=1}", result.getBody());
9596

9697
}
9798

@@ -102,7 +103,7 @@ public void removeFailedNestedPathVariables() {
102103
restTemplate.getForEntity("http://localhost:" + port + "/qux/quux", String.class);
103104

104105
assertEquals(HttpStatus.OK, result.getStatusCode());
105-
assertEquals("{qux=qux}", result.getBody());
106+
assertEquals("/{qux}/quux\n{qux=qux}", result.getBody());
106107

107108
}
108109

@@ -120,12 +121,9 @@ public void removeFailedPathVariablesAnd() {
120121

121122
private static class NestedHandler {
122123

123-
public Mono<ServerResponse> bar(ServerRequest request) {
124-
return ServerResponse.ok().syncBody("bar");
125-
}
126-
127-
public Mono<ServerResponse> baz(ServerRequest request) {
128-
return ServerResponse.ok().syncBody("baz");
124+
public Mono<ServerResponse> pattern(ServerRequest request) {
125+
String pattern = matchingPattern(request);
126+
return ServerResponse.ok().syncBody(pattern);
129127
}
130128

131129
@SuppressWarnings("unchecked")
@@ -136,10 +134,20 @@ public Mono<ServerResponse> variables(ServerRequest request) {
136134
assertTrue( (pathVariables.equals(attributePathVariables))
137135
|| (pathVariables.isEmpty() && (attributePathVariables == null)));
138136

139-
Mono<String> responseBody = Mono.just(pathVariables.toString());
137+
String pattern = matchingPattern(request);
138+
Flux<String> responseBody;
139+
if (!pattern.isEmpty()) {
140+
responseBody = Flux.just(pattern, "\n", pathVariables.toString());
141+
} else {
142+
responseBody = Flux.just(pathVariables.toString());
143+
}
140144
return ServerResponse.ok().body(responseBody, String.class);
141145
}
142146

147+
private String matchingPattern(ServerRequest request) {
148+
return (String) request.attributes().getOrDefault(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, "");
149+
}
150+
143151
}
144152

145153
}

0 commit comments

Comments
 (0)