Skip to content

Commit 4054dc7

Browse files
committed
Revise handler hierarchy in UrlHandlerFilter
See gh-32830
1 parent 6ad8d6e commit 4054dc7

File tree

1 file changed

+56
-82
lines changed

1 file changed

+56
-82
lines changed

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

Lines changed: 56 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.function.Consumer;
24-
import java.util.function.Function;
25-
import java.util.function.Predicate;
2624

2725
import jakarta.servlet.FilterChain;
2826
import jakarta.servlet.ServletException;
@@ -67,10 +65,10 @@ public final class UrlHandlerFilter extends OncePerRequestFilter {
6765
private static final Log logger = LogFactory.getLog(UrlHandlerFilter.class);
6866

6967

70-
private final MultiValueMap<UrlHandler, PathPattern> handlers;
68+
private final MultiValueMap<Handler, PathPattern> handlers;
7169

7270

73-
private UrlHandlerFilter(MultiValueMap<UrlHandler, PathPattern> handlers) {
71+
private UrlHandlerFilter(MultiValueMap<Handler, PathPattern> handlers) {
7472
this.handlers = new LinkedMultiValueMap<>(handlers);
7573
}
7674

@@ -95,7 +93,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
9593
if (path == null) {
9694
path = ServletRequestPathUtils.parseAndCache(request);
9795
}
98-
for (Map.Entry<UrlHandler, List<PathPattern>> entry : this.handlers.entrySet()) {
96+
for (Map.Entry<Handler, List<PathPattern>> entry : this.handlers.entrySet()) {
9997
if (!entry.getKey().canHandle(request)) {
10098
continue;
10199
}
@@ -188,14 +186,14 @@ private static final class DefaultBuilder implements Builder {
188186

189187
private final PathPatternParser patternParser = new PathPatternParser();
190188

191-
private final MultiValueMap<UrlHandler, PathPattern> handlers = new LinkedMultiValueMap<>();
189+
private final MultiValueMap<Handler, PathPattern> handlers = new LinkedMultiValueMap<>();
192190

193191
@Override
194192
public TrailingSlashSpec trailingSlashHandler(String... patterns) {
195193
return new DefaultTrailingSlashSpec(patterns);
196194
}
197195

198-
private DefaultBuilder addHandler(List<PathPattern> pathPatterns, UrlHandler handler) {
196+
private DefaultBuilder addHandler(List<PathPattern> pathPatterns, Handler handler) {
199197
pathPatterns.forEach(pattern -> this.handlers.add(handler, pattern));
200198
return this;
201199
}
@@ -207,18 +205,10 @@ public UrlHandlerFilter build() {
207205

208206
private final class DefaultTrailingSlashSpec implements TrailingSlashSpec {
209207

210-
private static final Predicate<HttpServletRequest> trailingSlashPredicate =
211-
request -> request.getRequestURI().endsWith("/");
212-
213-
private static final Function<String, String> tralingSlashTrimFunction = path -> {
214-
int index = (StringUtils.hasLength(path) ? path.lastIndexOf('/') : -1);
215-
return (index != -1 ? path.substring(0, index) : path);
216-
};
217-
218208
private final List<PathPattern> pathPatterns;
219209

220210
@Nullable
221-
private Consumer<HttpServletRequest> requestConsumer;
211+
private Consumer<HttpServletRequest> interceptor;
222212

223213
private DefaultTrailingSlashSpec(String[] patterns) {
224214
this.pathPatterns = Arrays.stream(patterns)
@@ -229,33 +219,20 @@ private DefaultTrailingSlashSpec(String[] patterns) {
229219

230220
@Override
231221
public TrailingSlashSpec intercept(Consumer<HttpServletRequest> consumer) {
232-
this.requestConsumer = (this.requestConsumer != null ?
233-
this.requestConsumer.andThen(consumer) : consumer);
222+
this.interceptor = (this.interceptor != null ? this.interceptor.andThen(consumer) : consumer);
234223
return this;
235224
}
236225

237226
@Override
238227
public Builder redirect(HttpStatus status) {
239-
return DefaultBuilder.this.addHandler(
240-
this.pathPatterns, new RedirectUrlHandler(
241-
trailingSlashPredicate, tralingSlashTrimFunction, status, initRequestConsumer()));
228+
Handler handler = new RedirectTrailingSlashHandler(status, this.interceptor);
229+
return DefaultBuilder.this.addHandler(this.pathPatterns, handler);
242230
}
243231

244232
@Override
245233
public Builder wrapRequest() {
246-
return DefaultBuilder.this.addHandler(
247-
this.pathPatterns, new RequestWrappingUrlHandler(
248-
trailingSlashPredicate, tralingSlashTrimFunction, initRequestConsumer()));
249-
}
250-
251-
private Consumer<HttpServletRequest> initRequestConsumer() {
252-
return this.requestConsumer != null ? this.requestConsumer :
253-
(request -> {
254-
if (logger.isTraceEnabled()) {
255-
logger.trace("Trimmed trailing slash: " +
256-
request.getMethod() + " " + request.getRequestURI());
257-
}
258-
});
234+
Handler handler = new RequestWrappingTrailingSlashHandler(this.interceptor);
235+
return DefaultBuilder.this.addHandler(this.pathPatterns, handler);
259236
}
260237
}
261238
}
@@ -265,7 +242,7 @@ private Consumer<HttpServletRequest> initRequestConsumer() {
265242
/**
266243
* Internal handler to encapsulate different ways to handle a request.
267244
*/
268-
private interface UrlHandler {
245+
private interface Handler {
269246

270247
/**
271248
* Whether the handler handles the given request.
@@ -281,72 +258,66 @@ void handle(HttpServletRequest request, HttpServletResponse response, FilterChai
281258

282259

283260
/**
284-
* Base class for {@code UrlHandler} implementations.
261+
* Base class for trailing slash {@link Handler} implementations.
285262
*/
286-
private abstract static class AbstractUrlHandler implements UrlHandler {
287-
288-
private final Predicate<HttpServletRequest> requestPredicate;
263+
private abstract static class AbstractTrailingSlashHandler implements Handler {
289264

290-
private final Function<String, String> pathFunction;
291-
292-
private final Consumer<HttpServletRequest> requestConsumer;
265+
private static final Consumer<HttpServletRequest> defaultInterceptor = request -> {
266+
if (logger.isTraceEnabled()) {
267+
logger.trace("Handling trailing slash URL: " +
268+
request.getMethod() + " " + request.getRequestURI());
269+
}
270+
};
293271

294-
AbstractUrlHandler(
295-
Predicate<HttpServletRequest> requestPredicate, Function<String, String> pathFunction,
296-
Consumer<HttpServletRequest> requestConsumer) {
272+
private final Consumer<HttpServletRequest> interceptor;
297273

298-
this.requestPredicate = requestPredicate;
299-
this.pathFunction = pathFunction;
300-
this.requestConsumer = requestConsumer;
301-
}
302-
303-
protected Function<String, String> getPathFunction() {
304-
return this.pathFunction;
274+
protected AbstractTrailingSlashHandler(@Nullable Consumer<HttpServletRequest> interceptor) {
275+
this.interceptor = (interceptor != null ? interceptor : defaultInterceptor);
305276
}
306277

307278
@Override
308279
public boolean canHandle(HttpServletRequest request) {
309-
return this.requestPredicate.test(request);
280+
return request.getRequestURI().endsWith("/");
310281
}
311282

312283
@Override
313284
public void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
314285
throws ServletException, IOException {
315286

316-
this.requestConsumer.accept(request);
287+
this.interceptor.accept(request);
317288
handleInternal(request, response, chain);
318289
}
319290

320291
protected abstract void handleInternal(
321292
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
322293
throws ServletException, IOException;
294+
295+
protected String trimTrailingSlash(String path) {
296+
int index = (StringUtils.hasLength(path) ? path.lastIndexOf('/') : -1);
297+
return (index != -1 ? path.substring(0, index) : path);
298+
}
323299
}
324300

325301

326302
/**
327303
* Path handler that sends a redirect.
328304
*/
329-
private static final class RedirectUrlHandler extends AbstractUrlHandler {
305+
private static final class RedirectTrailingSlashHandler extends AbstractTrailingSlashHandler {
330306

331307
private final HttpStatus httpStatus;
332308

333-
RedirectUrlHandler(
334-
Predicate<HttpServletRequest> pathPredicate, Function<String, String> pathFunction,
335-
HttpStatus httpStatus, Consumer<HttpServletRequest> interceptor) {
336-
337-
super(pathPredicate, pathFunction, interceptor);
309+
RedirectTrailingSlashHandler(HttpStatus httpStatus, @Nullable Consumer<HttpServletRequest> interceptor) {
310+
super(interceptor);
338311
this.httpStatus = httpStatus;
339312
}
340313

341314
@Override
342315
public void handleInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
343316
throws IOException {
344317

345-
String location = getPathFunction().apply(request.getRequestURI());
346-
347318
response.resetBuffer();
348319
response.setStatus(this.httpStatus.value());
349-
response.setHeader(HttpHeaders.LOCATION, location);
320+
response.setHeader(HttpHeaders.LOCATION, trimTrailingSlash(request.getRequestURI()));
350321
response.flushBuffer();
351322
}
352323
}
@@ -355,20 +326,27 @@ public void handleInternal(HttpServletRequest request, HttpServletResponse respo
355326
/**
356327
* Path handler that wraps the request and continues processing.
357328
*/
358-
private static final class RequestWrappingUrlHandler extends AbstractUrlHandler {
329+
private static final class RequestWrappingTrailingSlashHandler extends AbstractTrailingSlashHandler {
359330

360-
RequestWrappingUrlHandler(
361-
Predicate<HttpServletRequest> pathPredicate, Function<String, String> pathFunction,
362-
Consumer<HttpServletRequest> interceptor) {
363-
364-
super(pathPredicate, pathFunction, interceptor);
331+
RequestWrappingTrailingSlashHandler(@Nullable Consumer<HttpServletRequest> interceptor) {
332+
super(interceptor);
365333
}
366334

367335
@Override
368336
public void handleInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
369337
throws ServletException, IOException {
370338

371-
request = new PathHttpServletRequestWrapper(request, getPathFunction());
339+
String servletPath = request.getServletPath();
340+
String pathInfo = request.getPathInfo();
341+
boolean hasPathInfo = StringUtils.hasText(pathInfo);
342+
343+
request = new TrailingSlashHttpServletRequest(
344+
request,
345+
trimTrailingSlash(request.getRequestURI()),
346+
trimTrailingSlash(request.getRequestURL().toString()),
347+
hasPathInfo ? servletPath : trimTrailingSlash(servletPath),
348+
hasPathInfo ? trimTrailingSlash(pathInfo) : pathInfo);
349+
372350
chain.doFilter(request, response);
373351
}
374352
}
@@ -377,7 +355,7 @@ public void handleInternal(HttpServletRequest request, HttpServletResponse respo
377355
/**
378356
* Wraps the request to return modified path information.
379357
*/
380-
private static class PathHttpServletRequestWrapper extends HttpServletRequestWrapper {
358+
private static class TrailingSlashHttpServletRequest extends HttpServletRequestWrapper {
381359

382360
private final String requestURI;
383361

@@ -387,18 +365,14 @@ private static class PathHttpServletRequestWrapper extends HttpServletRequestWra
387365

388366
private final String pathInfo;
389367

390-
PathHttpServletRequestWrapper(HttpServletRequest request, Function<String, String> pathFunction) {
368+
TrailingSlashHttpServletRequest(HttpServletRequest request,
369+
String requestURI, String requestURL, String servletPath, String pathInfo) {
370+
391371
super(request);
392-
this.requestURI = pathFunction.apply(request.getRequestURI());
393-
this.requestURL = new StringBuffer(pathFunction.apply(request.getRequestURL().toString()));
394-
if (StringUtils.hasText(request.getPathInfo())) {
395-
this.servletPath = request.getServletPath();
396-
this.pathInfo = pathFunction.apply(request.getPathInfo());
397-
}
398-
else {
399-
this.servletPath = pathFunction.apply(request.getServletPath());
400-
this.pathInfo = request.getPathInfo();
401-
}
372+
this.requestURI = requestURI;
373+
this.requestURL = new StringBuffer(requestURL);
374+
this.servletPath = servletPath;
375+
this.pathInfo = pathInfo;
402376
}
403377

404378
@Override

0 commit comments

Comments
 (0)