Skip to content

Commit 2535096

Browse files
committed
Improve path matching in RESTEasy Reactive for ambiguous path templates
The PathMatcher now returns list of all possible matches rather than just the first match. This is for case when the first match end up not matching the request path, the remaining, lower priority matches, can be tried.
1 parent ea60ff6 commit 2535096

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/PathMatcher.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jboss.resteasy.reactive.server.mapping;
22

3+
import java.util.ArrayList;
34
import java.util.Comparator;
45
import java.util.List;
56
import java.util.Set;
@@ -34,24 +35,28 @@ class PathMatcher<T> implements Dumpable {
3435
* @param path The relative path to match
3536
* @return The match match. This will never be null, however if none matched its value field will be
3637
*/
37-
PathMatch<T> match(String path) {
38+
List<PathMatch<T>> match(String path) {
3839
int length = path.length();
3940
final int[] lengths = this.lengths;
41+
ArrayList<PathMatch<T>> matches = new ArrayList<>(1);
4042
for (int i = 0; i < lengths.length; ++i) {
4143
int pathLength = lengths[i];
4244
if (pathLength == length) {
4345
SubstringMap.SubstringMatch<T> next = paths.get(path, length);
4446
if (next != null) {
45-
return new PathMatch<>(path, "", next.getValue());
47+
matches.add(new PathMatch<>(path, "", next.getValue()));
4648
}
4749
} else if (pathLength < length) {
4850
SubstringMap.SubstringMatch<T> next = paths.get(path, pathLength);
4951
if (next != null) {
50-
return new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue());
52+
matches.add(new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue()));
5153
}
5254
}
5355
}
54-
return defaultMatch(path);
56+
if (matches.isEmpty()) {
57+
matches.add(defaultMatch(path));
58+
}
59+
return matches;
5560
}
5661

5762
PathMatch<T> defaultMatch(String path) {

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Arrays;
55
import java.util.Collections;
66
import java.util.HashMap;
7+
import java.util.List;
78
import java.util.Map;
89
import java.util.function.BiConsumer;
910
import java.util.regex.Matcher;
@@ -48,9 +49,12 @@ public void accept(String stem, ArrayList<RequestPath<T>> list) {
4849
* @return best RequestMatch, or null if the path has no match
4950
*/
5051
public RequestMatch<T> map(String path) {
51-
var result = mapFromPathMatcher(path, requestPaths.match(path), 0);
52-
if (result != null) {
53-
return result;
52+
List<PathMatcher.PathMatch<ArrayList<RequestPath<T>>>> matches = requestPaths.match(path);
53+
for (int i = 0; i < matches.size(); i++) {
54+
var result = mapFromPathMatcher(path, matches.get(i), 0);
55+
if (result != null) {
56+
return result;
57+
}
5458
}
5559

5660
// the following code is meant to handle cases like https://github.com/quarkusio/quarkus/issues/30667
@@ -68,16 +72,21 @@ public RequestMatch<T> continueMatching(String path, RequestMatch<T> lastMatch)
6872
return null;
6973
}
7074

71-
var initialMatches = requestPaths.match(path);
72-
var result = mapFromPathMatcher(path, initialMatches, 0);
73-
if (result != null) {
74-
int idx = nextMatchStartingIndex(initialMatches, lastMatch);
75-
return mapFromPathMatcher(path, initialMatches, idx);
75+
var initialMatchesList = requestPaths.match(path);
76+
for (int i = 0; i < initialMatchesList.size(); i++) {
77+
var result = mapFromPathMatcher(path, initialMatchesList.get(i), 0);
78+
if (result != null) {
79+
int idx = nextMatchStartingIndex(initialMatchesList.get(i), lastMatch);
80+
RequestMatch<T> match = mapFromPathMatcher(path, initialMatchesList.get(i), idx);
81+
if (match != null) {
82+
return match;
83+
}
84+
}
7685
}
7786

7887
// the following code is meant to handle cases like https://github.com/quarkusio/quarkus/issues/30667
79-
initialMatches = requestPaths.defaultMatch(path);
80-
result = mapFromPathMatcher(path, initialMatches, 0);
88+
var initialMatches = requestPaths.defaultMatch(path);
89+
var result = mapFromPathMatcher(path, initialMatches, 0);
8190
if (result != null) {
8291
int idx = nextMatchStartingIndex(initialMatches, lastMatch);
8392
return mapFromPathMatcher(path, initialMatches, idx);

independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PathParamOverlapTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,36 @@ public void test() {
5353
get("/hello/other/test/wrong")
5454
.then()
5555
.statusCode(404);
56+
57+
get("/hello/foo")
58+
.then()
59+
.statusCode(404);
60+
61+
get("/hello/foo/value")
62+
.then()
63+
.statusCode(200)
64+
.body(equalTo("Foo value"));
65+
66+
get("/hello/foo/bar")
67+
.then()
68+
.statusCode(200)
69+
.body(equalTo("Foo bar"));
70+
71+
get("/hello/foo/bar/value")
72+
.then()
73+
.statusCode(200)
74+
.body(equalTo("FooBar value"));
75+
76+
get("/hello/foo/bah_value")
77+
.then()
78+
.statusCode(200)
79+
.body(equalTo("Foo bah_value"));
80+
81+
get("/hello/foo/bar_value")
82+
.then()
83+
.statusCode(200)
84+
.body(equalTo("Foo bar_value"));
85+
5686
}
5787

5888
@Path("/hello")
@@ -70,5 +100,19 @@ public String test() {
70100
public String second(@RestPath String id) {
71101
return "Hello " + id;
72102
}
103+
104+
@GET
105+
@Path("/foo/{param}")
106+
public String foo(@RestPath String param) {
107+
return "Foo " + param;
108+
}
109+
110+
@GET
111+
@Path("/foo/bar/{param}")
112+
public String fooBar(@RestPath String param) {
113+
return "FooBar " + param;
114+
}
115+
73116
}
117+
74118
}

0 commit comments

Comments
 (0)