diff --git a/spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java b/spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java index 89e57bc795..36a771e71e 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/UrlHandlerFilter.java @@ -21,8 +21,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -67,10 +65,10 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { private static final Log logger = LogFactory.getLog(UrlHandlerFilter.class); - private final MultiValueMap handlers; + private final MultiValueMap handlers; - private UrlHandlerFilter(MultiValueMap handlers) { + private UrlHandlerFilter(MultiValueMap handlers) { this.handlers = new LinkedMultiValueMap<>(handlers); } @@ -95,7 +93,7 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { if (path == null) { path = ServletRequestPathUtils.parseAndCache(request); } - for (Map.Entry> entry : this.handlers.entrySet()) { + for (Map.Entry> entry : this.handlers.entrySet()) { if (!entry.getKey().canHandle(request)) { continue; } @@ -188,14 +186,14 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { private final PathPatternParser patternParser = new PathPatternParser(); - private final MultiValueMap handlers = new LinkedMultiValueMap<>(); + private final MultiValueMap handlers = new LinkedMultiValueMap<>(); @Override public TrailingSlashSpec trailingSlashHandler(String... patterns) { return new DefaultTrailingSlashSpec(patterns); } - private DefaultBuilder addHandler(List pathPatterns, UrlHandler handler) { + private DefaultBuilder addHandler(List pathPatterns, Handler handler) { pathPatterns.forEach(pattern -> this.handlers.add(handler, pattern)); return this; } @@ -207,18 +205,10 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { private final class DefaultTrailingSlashSpec implements TrailingSlashSpec { - private static final Predicate trailingSlashPredicate = - request -> request.getRequestURI().endsWith("/"); - - private static final Function tralingSlashTrimFunction = path -> { - int index = (StringUtils.hasLength(path) ? path.lastIndexOf('/') : -1); - return (index != -1 ? path.substring(0, index) : path); - }; - private final List pathPatterns; @Nullable - private Consumer requestConsumer; + private Consumer interceptor; private DefaultTrailingSlashSpec(String[] patterns) { this.pathPatterns = Arrays.stream(patterns) @@ -229,33 +219,20 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { @Override public TrailingSlashSpec intercept(Consumer consumer) { - this.requestConsumer = (this.requestConsumer != null ? - this.requestConsumer.andThen(consumer) : consumer); + this.interceptor = (this.interceptor != null ? this.interceptor.andThen(consumer) : consumer); return this; } @Override public Builder redirect(HttpStatus status) { - return DefaultBuilder.this.addHandler( - this.pathPatterns, new RedirectUrlHandler( - trailingSlashPredicate, tralingSlashTrimFunction, status, initRequestConsumer())); + Handler handler = new RedirectTrailingSlashHandler(status, this.interceptor); + return DefaultBuilder.this.addHandler(this.pathPatterns, handler); } @Override public Builder wrapRequest() { - return DefaultBuilder.this.addHandler( - this.pathPatterns, new RequestWrappingUrlHandler( - trailingSlashPredicate, tralingSlashTrimFunction, initRequestConsumer())); - } - - private Consumer initRequestConsumer() { - return this.requestConsumer != null ? this.requestConsumer : - (request -> { - if (logger.isTraceEnabled()) { - logger.trace("Trimmed trailing slash: " + - request.getMethod() + " " + request.getRequestURI()); - } - }); + Handler handler = new RequestWrappingTrailingSlashHandler(this.interceptor); + return DefaultBuilder.this.addHandler(this.pathPatterns, handler); } } } @@ -265,7 +242,7 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { /** * Internal handler to encapsulate different ways to handle a request. */ - private interface UrlHandler { + private interface Handler { /** * Whether the handler handles the given request. @@ -281,60 +258,56 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { /** - * Base class for {@code UrlHandler} implementations. + * Base class for trailing slash {@link Handler} implementations. */ - private abstract static class AbstractUrlHandler implements UrlHandler { + private abstract static class AbstractTrailingSlashHandler implements Handler { - private final Predicate requestPredicate; + private static final Consumer defaultInterceptor = request -> { + if (logger.isTraceEnabled()) { + logger.trace("Handling trailing slash URL: " + + request.getMethod() + " " + request.getRequestURI()); + } + }; - private final Function pathFunction; + private final Consumer interceptor; - private final Consumer requestConsumer; - - AbstractUrlHandler( - Predicate requestPredicate, Function pathFunction, - Consumer requestConsumer) { - - this.requestPredicate = requestPredicate; - this.pathFunction = pathFunction; - this.requestConsumer = requestConsumer; - } - - protected Function getPathFunction() { - return this.pathFunction; + protected AbstractTrailingSlashHandler(@Nullable Consumer interceptor) { + this.interceptor = (interceptor != null ? interceptor : defaultInterceptor); } @Override public boolean canHandle(HttpServletRequest request) { - return this.requestPredicate.test(request); + return request.getRequestURI().endsWith("/"); } @Override public void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - this.requestConsumer.accept(request); + this.interceptor.accept(request); handleInternal(request, response, chain); } protected abstract void handleInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException; + + protected String trimTrailingSlash(String path) { + int index = (StringUtils.hasLength(path) ? path.lastIndexOf('/') : -1); + return (index != -1 ? path.substring(0, index) : path); + } } /** * Path handler that sends a redirect. */ - private static final class RedirectUrlHandler extends AbstractUrlHandler { + private static final class RedirectTrailingSlashHandler extends AbstractTrailingSlashHandler { private final HttpStatus httpStatus; - RedirectUrlHandler( - Predicate pathPredicate, Function pathFunction, - HttpStatus httpStatus, Consumer interceptor) { - - super(pathPredicate, pathFunction, interceptor); + RedirectTrailingSlashHandler(HttpStatus httpStatus, @Nullable Consumer interceptor) { + super(interceptor); this.httpStatus = httpStatus; } @@ -342,11 +315,9 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { public void handleInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException { - String location = getPathFunction().apply(request.getRequestURI()); - response.resetBuffer(); response.setStatus(this.httpStatus.value()); - response.setHeader(HttpHeaders.LOCATION, location); + response.setHeader(HttpHeaders.LOCATION, trimTrailingSlash(request.getRequestURI())); response.flushBuffer(); } } @@ -355,20 +326,27 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { /** * Path handler that wraps the request and continues processing. */ - private static final class RequestWrappingUrlHandler extends AbstractUrlHandler { + private static final class RequestWrappingTrailingSlashHandler extends AbstractTrailingSlashHandler { - RequestWrappingUrlHandler( - Predicate pathPredicate, Function pathFunction, - Consumer interceptor) { - - super(pathPredicate, pathFunction, interceptor); + RequestWrappingTrailingSlashHandler(@Nullable Consumer interceptor) { + super(interceptor); } @Override public void handleInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - request = new PathHttpServletRequestWrapper(request, getPathFunction()); + String servletPath = request.getServletPath(); + String pathInfo = request.getPathInfo(); + boolean hasPathInfo = StringUtils.hasText(pathInfo); + + request = new TrailingSlashHttpServletRequest( + request, + trimTrailingSlash(request.getRequestURI()), + trimTrailingSlash(request.getRequestURL().toString()), + hasPathInfo ? servletPath : trimTrailingSlash(servletPath), + hasPathInfo ? trimTrailingSlash(pathInfo) : pathInfo); + chain.doFilter(request, response); } } @@ -377,7 +355,7 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { /** * Wraps the request to return modified path information. */ - private static class PathHttpServletRequestWrapper extends HttpServletRequestWrapper { + private static class TrailingSlashHttpServletRequest extends HttpServletRequestWrapper { private final String requestURI; @@ -387,18 +365,14 @@ public final class UrlHandlerFilter extends OncePerRequestFilter { private final String pathInfo; - PathHttpServletRequestWrapper(HttpServletRequest request, Function pathFunction) { + TrailingSlashHttpServletRequest(HttpServletRequest request, + String requestURI, String requestURL, String servletPath, String pathInfo) { + super(request); - this.requestURI = pathFunction.apply(request.getRequestURI()); - this.requestURL = new StringBuffer(pathFunction.apply(request.getRequestURL().toString())); - if (StringUtils.hasText(request.getPathInfo())) { - this.servletPath = request.getServletPath(); - this.pathInfo = pathFunction.apply(request.getPathInfo()); - } - else { - this.servletPath = pathFunction.apply(request.getServletPath()); - this.pathInfo = request.getPathInfo(); - } + this.requestURI = requestURI; + this.requestURL = new StringBuffer(requestURL); + this.servletPath = servletPath; + this.pathInfo = pathInfo; } @Override