Skip to content

Commit 132ed2b

Browse files
committed
Add Cache Filter for Parsed RequestPath
This exposes the management of the parsed and cached RequestPath. It will be helpful in filter chains so the RequestPath is only parsed once per dispatch. Signed-off-by: Josh Cummings <[email protected]>
1 parent bb7a800 commit 132ed2b

File tree

2 files changed

+73
-3
lines changed

2 files changed

+73
-3
lines changed

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

+29
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616

1717
package org.springframework.web.util;
1818

19+
import java.io.IOException;
1920
import java.nio.charset.StandardCharsets;
2021
import java.util.List;
2122

23+
import jakarta.servlet.Filter;
24+
import jakarta.servlet.FilterChain;
2225
import jakarta.servlet.RequestDispatcher;
26+
import jakarta.servlet.ServletException;
2327
import jakarta.servlet.ServletRequest;
28+
import jakarta.servlet.ServletResponse;
2429
import jakarta.servlet.http.HttpServletMapping;
2530
import jakarta.servlet.http.HttpServletRequest;
2631
import jakarta.servlet.http.MappingMatch;
@@ -110,6 +115,14 @@ public static void clearParsedRequestPath(ServletRequest request) {
110115
request.removeAttribute(PATH_ATTRIBUTE);
111116
}
112117

118+
/**
119+
* Provide a {@link Filter} that manages the parsing and caching of a {@link RequestPath}.
120+
* @return the described {@link Filter}
121+
* @since 6.2.3
122+
*/
123+
public static Filter getParsedRequestPathCacheFilter() {
124+
return new RequestPathCacheFilter();
125+
}
113126

114127
// Methods to select either parsed RequestPath or resolved String lookupPath
115128

@@ -312,4 +325,20 @@ PathElements withContextPath(String contextPath) {
312325
}
313326
}
314327

328+
private static final class RequestPathCacheFilter implements Filter {
329+
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
330+
if (!(req instanceof HttpServletRequest request)) {
331+
chain.doFilter(req, res);
332+
return;
333+
}
334+
RequestPath previousRequestPath = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
335+
try {
336+
parseAndCache(request);
337+
chain.doFilter(req, res);
338+
}
339+
finally {
340+
setParsedRequestPath(previousRequestPath, request);
341+
}
342+
}
343+
}
315344
}

spring-web/src/test/java/org/springframework/web/util/ServletRequestPathUtilsTests.java

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,12 @@
1616

1717
package org.springframework.web.util;
1818

19+
import java.io.IOException;
20+
21+
import jakarta.servlet.Filter;
22+
import jakarta.servlet.RequestDispatcher;
23+
import jakarta.servlet.ServletException;
24+
import jakarta.servlet.http.HttpServletMapping;
1925
import jakarta.servlet.http.MappingMatch;
2026
import org.junit.jupiter.api.Test;
2127

@@ -88,6 +94,35 @@ void modifyPathContextWithContextPathEndingWithSlash() {
8894
.withMessage("Invalid contextPath '/persons/': must start with '/' and not end with '/'");
8995
}
9096

97+
@Test
98+
void filterParsesAndCachesThenCleansUp() throws IOException, ServletException {
99+
MockHttpServletRequest request = createRequest("/servlet/request", "", "/servlet", "/request");
100+
Filter filter = ServletRequestPathUtils.getParsedRequestPathCacheFilter();
101+
filter.doFilter(request, null, (req, res) -> {
102+
RequestPath currentPath = ServletRequestPathUtils.getParsedRequestPath(request);
103+
assertThat(currentPath.pathWithinApplication().value()).isEqualTo("/request");
104+
});
105+
assertThat(ServletRequestPathUtils.hasParsedRequestPath(request)).isFalse();
106+
}
107+
108+
@Test
109+
void filterParsesCachesAndThenRestores() throws IOException, ServletException {
110+
MockHttpServletRequest request = createRequest("/servlet/request", "", "/servlet", "/request");
111+
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);
112+
113+
HttpServletMapping mapping = new MockHttpServletMapping("/include", "", "myServlet", MappingMatch.PATH);
114+
request.setAttribute(RequestDispatcher.INCLUDE_MAPPING, mapping);
115+
request.setAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE, "/servlet");
116+
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/servlet/include");
117+
118+
Filter filter = ServletRequestPathUtils.getParsedRequestPathCacheFilter();
119+
filter.doFilter(request, null, (req, res) -> {
120+
RequestPath currentPath = ServletRequestPathUtils.getParsedRequestPath(request);
121+
assertThat(currentPath.pathWithinApplication().value()).isEqualTo("/include");
122+
});
123+
assertThat(requestPath).isEqualTo(ServletRequestPathUtils.getParsedRequestPath(request));
124+
}
125+
91126
private void testParseAndCache(
92127
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {
93128

@@ -100,13 +135,19 @@ private void testParseAndCache(
100135
private static RequestPath createRequestPath(
101136
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {
102137

138+
MockHttpServletRequest request = createRequest(requestUri, contextPath, servletPath, pathWithinApplication);
139+
return ServletRequestPathUtils.parseAndCache(request);
140+
}
141+
142+
private static MockHttpServletRequest createRequest(
143+
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {
144+
103145
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
104146
request.setContextPath(contextPath);
105147
request.setServletPath(servletPath);
106148
request.setHttpServletMapping(new MockHttpServletMapping(
107149
pathWithinApplication, contextPath, "myServlet", MappingMatch.PATH));
108-
109-
return ServletRequestPathUtils.parseAndCache(request);
150+
return request;
110151
}
111152

112153
}

0 commit comments

Comments
 (0)