Skip to content

Commit 6f150e4

Browse files
committed
SPR-8898 Allow match by trailing slash in RequestMappingHandlerMapping.
1 parent 569426d commit 6f150e4

File tree

5 files changed

+59
-27
lines changed

5 files changed

+59
-27
lines changed

build-spring-framework/resources/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Changes in version 3.1 GA (2011-12-12)
1919
* ResourceHttpRequestHandler and ContentNegotiatingViewResolver use consistent mime type resolution
2020
* Portlet MVC annotation mapping allows for distributing action names across controllers
2121
* added String constants to MediaType
22+
* add useTrailingSlashMatch property to RequestMappingHandlerMapping
2223

2324

2425
Changes in version 3.1 RC2 (2011-11-28)

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ public final class PatternsRequestCondition extends AbstractRequestCondition<Pat
5050

5151
private final boolean useSuffixPatternMatch;
5252

53+
private final boolean useTrailingSlashMatch;
54+
5355
/**
5456
* Creates a new instance with the given URL patterns.
5557
* Each pattern that is not empty and does not start with "/" is pre-pended with "/".
5658
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
5759
*/
5860
public PatternsRequestCondition(String... patterns) {
59-
this(asList(patterns), null, null, true);
61+
this(asList(patterns), null, null, true, true);
6062
}
6163

6264
/**
@@ -66,12 +68,14 @@ public PatternsRequestCondition(String... patterns) {
6668
* @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
6769
* @param pathMatcher a {@link PathMatcher} for pattern path matching
6870
* @param useSuffixPatternMatch whether to enable matching by suffix (".*")
71+
* @param useTrailingSlashMatch whether to match irrespective of a trailing slash
6972
*/
7073
public PatternsRequestCondition(String[] patterns,
7174
UrlPathHelper urlPathHelper,
7275
PathMatcher pathMatcher,
73-
boolean useSuffixPatternMatch) {
74-
this(asList(patterns), urlPathHelper, pathMatcher, useSuffixPatternMatch);
76+
boolean useSuffixPatternMatch,
77+
boolean useTrailingSlashMatch) {
78+
this(asList(patterns), urlPathHelper, pathMatcher, useSuffixPatternMatch, useTrailingSlashMatch);
7579
}
7680

7781
/**
@@ -80,11 +84,13 @@ public PatternsRequestCondition(String[] patterns,
8084
private PatternsRequestCondition(Collection<String> patterns,
8185
UrlPathHelper urlPathHelper,
8286
PathMatcher pathMatcher,
83-
boolean useSuffixPatternMatch) {
87+
boolean useSuffixPatternMatch,
88+
boolean useTrailingSlashMatch) {
8489
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns));
8590
this.urlPathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper();
8691
this.pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher();
8792
this.useSuffixPatternMatch = useSuffixPatternMatch;
93+
this.useTrailingSlashMatch = useTrailingSlashMatch;
8894
}
8995

9096
private static List<String> asList(String... patterns) {
@@ -106,12 +112,12 @@ private static Set<String> prependLeadingSlash(Collection<String> patterns) {
106112
}
107113

108114
public Set<String> getPatterns() {
109-
return patterns;
115+
return this.patterns;
110116
}
111117

112118
@Override
113119
protected Collection<String> getContent() {
114-
return patterns;
120+
return this.patterns;
115121
}
116122

117123
@Override
@@ -134,7 +140,7 @@ public PatternsRequestCondition combine(PatternsRequestCondition other) {
134140
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
135141
for (String pattern1 : this.patterns) {
136142
for (String pattern2 : other.patterns) {
137-
result.add(pathMatcher.combine(pattern1, pattern2));
143+
result.add(this.pathMatcher.combine(pattern1, pattern2));
138144
}
139145
}
140146
}
@@ -147,7 +153,8 @@ else if (!other.patterns.isEmpty()) {
147153
else {
148154
result.add("");
149155
}
150-
return new PatternsRequestCondition(result, urlPathHelper, pathMatcher, useSuffixPatternMatch);
156+
return new PatternsRequestCondition(result, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
157+
this.useTrailingSlashMatch);
151158
}
152159

153160
/**
@@ -170,38 +177,41 @@ else if (!other.patterns.isEmpty()) {
170177
* or {@code null} if no patterns match.
171178
*/
172179
public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
173-
if (patterns.isEmpty()) {
180+
if (this.patterns.isEmpty()) {
174181
return this;
175182
}
176-
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
183+
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
177184
List<String> matches = new ArrayList<String>();
178185
for (String pattern : patterns) {
179186
String match = getMatchingPattern(pattern, lookupPath);
180187
if (match != null) {
181188
matches.add(match);
182189
}
183190
}
184-
Collections.sort(matches, pathMatcher.getPatternComparator(lookupPath));
191+
Collections.sort(matches, this.pathMatcher.getPatternComparator(lookupPath));
185192
return matches.isEmpty() ? null :
186-
new PatternsRequestCondition(matches, urlPathHelper, pathMatcher, useSuffixPatternMatch);
193+
new PatternsRequestCondition(matches, this.urlPathHelper, this.pathMatcher, this.useSuffixPatternMatch,
194+
this.useTrailingSlashMatch);
187195
}
188196

189197
private String getMatchingPattern(String pattern, String lookupPath) {
190198
if (pattern.equals(lookupPath)) {
191199
return pattern;
192200
}
193-
if (useSuffixPatternMatch) {
201+
if (this.useSuffixPatternMatch) {
194202
boolean hasSuffix = pattern.indexOf('.') != -1;
195-
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
203+
if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
196204
return pattern + ".*";
197205
}
198206
}
199-
if (pathMatcher.match(pattern, lookupPath)) {
207+
if (this.pathMatcher.match(pattern, lookupPath)) {
200208
return pattern;
201209
}
202210
boolean endsWithSlash = pattern.endsWith("/");
203-
if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
204-
return pattern +"/";
211+
if (this.useTrailingSlashMatch) {
212+
if (!endsWithSlash && this.pathMatcher.match(pattern + "/", lookupPath)) {
213+
return pattern +"/";
214+
}
205215
}
206216
return null;
207217
}
@@ -219,8 +229,8 @@ private String getMatchingPattern(String pattern, String lookupPath) {
219229
* the best matches on top.
220230
*/
221231
public int compareTo(PatternsRequestCondition other, HttpServletRequest request) {
222-
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
223-
Comparator<String> patternComparator = pathMatcher.getPatternComparator(lookupPath);
232+
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
233+
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(lookupPath);
224234

225235
Iterator<String> iterator = patterns.iterator();
226236
Iterator<String> iteratorOther = other.patterns.iterator();

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,38 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
4444

4545
private boolean useSuffixPatternMatch = true;
4646

47+
private boolean useTrailingSlashMatch = true;
48+
4749
/**
4850
* Whether to use suffix pattern match (".*") when matching patterns to
49-
* requests. If enabled a method mapped to "/users" also matches to
50-
* "/users.*". The default value is "true".
51+
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
52+
* <p>The default value is {@code true}.
5153
*/
5254
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) {
5355
this.useSuffixPatternMatch = useSuffixPatternMatch;
5456
}
57+
58+
/**
59+
* Whether to match to URLs irrespective of the presence of a trailing slash.
60+
* If enabled a method mapped to "/users" also matches to "/users/".
61+
* <p>The default value is {@code true}.
62+
*/
63+
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch) {
64+
this.useTrailingSlashMatch = useTrailingSlashMatch;
65+
}
5566

5667
/**
5768
* Whether to use suffix pattern matching.
5869
*/
5970
public boolean useSuffixPatternMatch() {
6071
return this.useSuffixPatternMatch;
6172
}
73+
/**
74+
* Whether to match to URLs irrespective of the presence of a trailing slash.
75+
*/
76+
public boolean useTrailingSlashMatch() {
77+
return this.useTrailingSlashMatch;
78+
}
6279

6380
/**
6481
* {@inheritDoc}
@@ -125,7 +142,7 @@ protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
125142
private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
126143
return new RequestMappingInfo(
127144
new PatternsRequestCondition(annotation.value(),
128-
getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch),
145+
getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch),
129146
new RequestMethodsRequestCondition(annotation.method()),
130147
new ParamsRequestCondition(annotation.params()),
131148
new HeadersRequestCondition(annotation.headers()),

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/condition/PatternsRequestConditionTests.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public void matchSuffixPattern() {
106106
assertNotNull(match);
107107
assertEquals("/{foo}.*", match.getPatterns().iterator().next());
108108

109-
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false);
109+
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false);
110110
match = condition.getMatchingCondition(request);
111111

112112
assertNotNull(match);
@@ -121,15 +121,19 @@ public void matchTrailingSlash() {
121121
PatternsRequestCondition match = condition.getMatchingCondition(request);
122122

123123
assertNotNull(match);
124-
assertEquals("/foo/", match.getPatterns().iterator().next());
124+
assertEquals("Should match by default", "/foo/", match.getPatterns().iterator().next());
125125

126-
boolean useSuffixPatternMatch = false;
127-
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, useSuffixPatternMatch);
126+
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true);
128127
match = condition.getMatchingCondition(request);
129128

130129
assertNotNull(match);
131130
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
132131
"/foo/", match.getPatterns().iterator().next());
132+
133+
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false);
134+
match = condition.getMatchingCondition(request);
135+
136+
assertNull(match);
133137
}
134138

135139
@Test

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handler
317317
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
318318
if (annotation != null) {
319319
return new RequestMappingInfo(
320-
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true),
320+
new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true, true),
321321
new RequestMethodsRequestCondition(annotation.method()),
322322
new ParamsRequestCondition(annotation.params()),
323323
new HeadersRequestCondition(annotation.headers()),

0 commit comments

Comments
 (0)