Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@

package org.springframework.web.util;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.MappingMatch;
Expand Down Expand Up @@ -110,6 +115,14 @@ public static void clearParsedRequestPath(ServletRequest request) {
request.removeAttribute(PATH_ATTRIBUTE);
}

/**
* Provide a {@link Filter} that manages the parsing and caching of a {@link RequestPath}.
* @return the described {@link Filter}
* @since 6.2.3
*/
public static Filter getParsedRequestPathCacheFilter() {
return new RequestPathCacheFilter();
}

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

Expand Down Expand Up @@ -312,4 +325,20 @@ PathElements withContextPath(String contextPath) {
}
}

private static final class RequestPathCacheFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (!(req instanceof HttpServletRequest request)) {
chain.doFilter(req, res);
return;
}
RequestPath previousRequestPath = (RequestPath) request.getAttribute(PATH_ATTRIBUTE);
try {
parseAndCache(request);
chain.doFilter(req, res);
}
finally {
setParsedRequestPath(previousRequestPath, request);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,12 @@

package org.springframework.web.util;

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.MappingMatch;
import org.junit.jupiter.api.Test;

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

@Test
void filterParsesAndCachesThenCleansUp() throws IOException, ServletException {
MockHttpServletRequest request = createRequest("/servlet/request", "", "/servlet", "/request");
Filter filter = ServletRequestPathUtils.getParsedRequestPathCacheFilter();
filter.doFilter(request, null, (req, res) -> {
RequestPath currentPath = ServletRequestPathUtils.getParsedRequestPath(request);
assertThat(currentPath.pathWithinApplication().value()).isEqualTo("/request");
});
assertThat(ServletRequestPathUtils.hasParsedRequestPath(request)).isFalse();
}

@Test
void filterParsesCachesAndThenRestores() throws IOException, ServletException {
MockHttpServletRequest request = createRequest("/servlet/request", "", "/servlet", "/request");
RequestPath requestPath = ServletRequestPathUtils.parseAndCache(request);

HttpServletMapping mapping = new MockHttpServletMapping("/include", "", "myServlet", MappingMatch.PATH);
request.setAttribute(RequestDispatcher.INCLUDE_MAPPING, mapping);
request.setAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE, "/servlet");
request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/servlet/include");

Filter filter = ServletRequestPathUtils.getParsedRequestPathCacheFilter();
filter.doFilter(request, null, (req, res) -> {
RequestPath currentPath = ServletRequestPathUtils.getParsedRequestPath(request);
assertThat(currentPath.pathWithinApplication().value()).isEqualTo("/include");
});
assertThat(requestPath).isEqualTo(ServletRequestPathUtils.getParsedRequestPath(request));
}

private void testParseAndCache(
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {

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

MockHttpServletRequest request = createRequest(requestUri, contextPath, servletPath, pathWithinApplication);
return ServletRequestPathUtils.parseAndCache(request);
}

private static MockHttpServletRequest createRequest(
String requestUri, String contextPath, String servletPath, String pathWithinApplication) {

MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setContextPath(contextPath);
request.setServletPath(servletPath);
request.setHttpServletMapping(new MockHttpServletMapping(
pathWithinApplication, contextPath, "myServlet", MappingMatch.PATH));

return ServletRequestPathUtils.parseAndCache(request);
return request;
}

}