Skip to content

Commit 5a85891

Browse files
committed
Revise parsed path handling in UrlHandlerFilter
Closes gh-35538
1 parent d85a020 commit 5a85891

File tree

3 files changed

+97
-37
lines changed

3 files changed

+97
-37
lines changed

spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -74,43 +74,25 @@ private UrlHandlerFilter(MultiValueMap<Handler, PathPattern> handlers) {
7474
}
7575

7676

77-
@Override
78-
protected boolean shouldNotFilterAsyncDispatch() {
79-
return false;
80-
}
81-
82-
@Override
83-
protected boolean shouldNotFilterErrorDispatch() {
84-
return false;
85-
}
86-
8777
@Override
8878
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
8979
throws ServletException, IOException {
9080

91-
RequestPath previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
92-
RequestPath path = previousPath;
93-
try {
94-
if (path == null) {
95-
path = ServletRequestPathUtils.parseAndCache(request);
81+
RequestPath path = (ServletRequestPathUtils.hasParsedRequestPath(request) ?
82+
ServletRequestPathUtils.getParsedRequestPath(request) :
83+
ServletRequestPathUtils.parse(request));
84+
85+
for (Map.Entry<Handler, List<PathPattern>> entry : this.handlers.entrySet()) {
86+
if (!entry.getKey().supports(request, path)) {
87+
continue;
9688
}
97-
for (Map.Entry<Handler, List<PathPattern>> entry : this.handlers.entrySet()) {
98-
if (!entry.getKey().supports(request, path)) {
99-
continue;
100-
}
101-
for (PathPattern pattern : entry.getValue()) {
102-
if (pattern.matches(path)) {
103-
entry.getKey().handle(request, response, chain);
104-
return;
105-
}
89+
for (PathPattern pattern : entry.getValue()) {
90+
if (pattern.matches(path)) {
91+
entry.getKey().handle(request, response, chain);
92+
return;
10693
}
10794
}
10895
}
109-
finally {
110-
if (previousPath != null) {
111-
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
112-
}
113-
}
11496

11597
chain.doFilter(request, response);
11698
}
@@ -349,7 +331,18 @@ public void handleInternal(HttpServletRequest request, HttpServletResponse respo
349331
hasPathInfo ? servletPath : trimTrailingSlash(servletPath),
350332
hasPathInfo ? trimTrailingSlash(pathInfo) : pathInfo);
351333

352-
chain.doFilter(request, response);
334+
RequestPath previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
335+
if (previousPath != null) {
336+
ServletRequestPathUtils.parseAndCache(request);
337+
}
338+
try {
339+
chain.doFilter(request, response);
340+
}
341+
finally {
342+
if (previousPath != null) {
343+
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
344+
}
345+
}
353346
}
354347
}
355348

spring-web/src/main/java/org/springframework/web/util/ServletRequestPathUtils.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,19 @@ public abstract class ServletRequestPathUtils {
5252

5353
/**
5454
* Parse the {@link HttpServletRequest#getRequestURI() requestURI} to a
55-
* {@link RequestPath} and save it in the request attribute
56-
* {@link #PATH_ATTRIBUTE} for subsequent use with
57-
* {@link org.springframework.web.util.pattern.PathPattern parsed patterns}.
55+
* {@link RequestPath}.
5856
* <p>The returned {@code RequestPath} will have both the contextPath and any
5957
* servletPath prefix omitted from the {@link RequestPath#pathWithinApplication()
6058
* pathWithinApplication} it exposes.
61-
* <p>This method is typically called by the {@code DispatcherServlet} to determine
62-
* if any {@code HandlerMapping} indicates that it uses parsed patterns.
63-
* After that the pre-parsed and cached {@code RequestPath} can be accessed
64-
* through {@link #getParsedRequestPath(ServletRequest)}.
59+
* @since 6.2.12
60+
*/
61+
public static RequestPath parse(HttpServletRequest request) {
62+
return ServletRequestPath.parse(request);
63+
}
64+
65+
/**
66+
* Variant of {@link #parse(HttpServletRequest)} that also saves the parsed
67+
* path in the request attribute {@link #PATH_ATTRIBUTE}.
6568
*/
6669
public static RequestPath parseAndCache(HttpServletRequest request) {
6770
RequestPath requestPath = ServletRequestPath.parse(request);

spring-web/src/test/java/org/springframework/web/filter/UrlHandlerFilterTests.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.io.IOException;
2020

2121
import jakarta.servlet.ServletException;
22+
import jakarta.servlet.http.HttpServlet;
2223
import jakarta.servlet.http.HttpServletRequest;
24+
import jakarta.servlet.http.HttpServletResponse;
2325
import org.junit.jupiter.api.Test;
2426

2527
import org.springframework.http.HttpHeaders;
@@ -29,6 +31,7 @@
2931
import org.springframework.web.testfixture.servlet.MockFilterChain;
3032
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
3133
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
34+
import org.springframework.web.util.ServletRequestPathUtils;
3235

3336
import static org.assertj.core.api.Assertions.assertThat;
3437

@@ -124,4 +127,65 @@ private static void testNoUrlHandling(String pattern, String contextPath, String
124127
assertThat(response.isCommitted()).isFalse();
125128
}
126129

130+
@Test
131+
void shouldNotFilterErrorAndAsyncDispatches() {
132+
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/**").wrapRequest().build();
133+
134+
assertThat(filter.shouldNotFilterAsyncDispatch())
135+
.as("Should not filter async dispatch: wrapped request is reused")
136+
.isTrue();
137+
138+
assertThat(filter.shouldNotFilterErrorDispatch())
139+
.as("Should not filter error dispatch: it's a different path")
140+
.isTrue();
141+
}
142+
143+
@Test
144+
void shouldNotCacheParsedPath() throws Exception {
145+
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/*").wrapRequest().build();
146+
147+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path/123/");
148+
request.setServletPath("/path/123/");
149+
150+
MockFilterChain chain = new MockFilterChain();
151+
filter.doFilterInternal(request, new MockHttpServletResponse(), chain);
152+
153+
assertThat(ServletRequestPathUtils.hasParsedRequestPath(request))
154+
.as("Path with trailing slash should not be cached")
155+
.isFalse();
156+
}
157+
158+
@Test
159+
void shouldReplaceCachedPath() throws Exception {
160+
UrlHandlerFilter filter = UrlHandlerFilter.trailingSlashHandler("/path/*").wrapRequest().build();
161+
162+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path/123/");
163+
request.setServletPath("/path/123/");
164+
165+
ServletRequestPathUtils.parseAndCache(request);
166+
assertThat(ServletRequestPathUtils.getParsedRequestPath(request).value()).isEqualTo("/path/123/");
167+
168+
PathSavingServlet servlet = new PathSavingServlet();
169+
MockFilterChain chain = new MockFilterChain(servlet);
170+
filter.doFilterInternal(request, new MockHttpServletResponse(), chain);
171+
172+
assertThat(servlet.getParsedPath()).isEqualTo("/path/123");
173+
assertThat(ServletRequestPathUtils.getParsedRequestPath(request).value()).isEqualTo("/path/123/");
174+
}
175+
176+
177+
private static class PathSavingServlet extends HttpServlet {
178+
179+
private String parsedPath;
180+
181+
public String getParsedPath() {
182+
return parsedPath;
183+
}
184+
185+
@Override
186+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
187+
this.parsedPath = ServletRequestPathUtils.getParsedRequestPath(request).value();
188+
}
189+
}
190+
127191
}

0 commit comments

Comments
 (0)