If you use the UrlHandlerFilter
to ignore or remove trailing slash in URLs, when you call RequestDispatcher.forward()
from a filter, a servlet or a controller, it will not work as expected if the requested url ends with slash.
For example you configure this filter as the first one in your server:
@Bean
public Filter urlTrailingSlashFilter() {
return UrlHandlerFilter
.trailingSlashHandler("/**").wrapRequest()
.build();
}
If the request does not end with slash, the original request is used in any call to request.getRequestDispatcher("/other_path").forward(request, response);
At least using Tomcat, that makes the request getRedirectURI
method to return "/other_path" in the destination servlet, so if the request is processed by the Spring DispatcherServlet
, it is correctly routed to any controller mapped in "/other_path".
If the request ends with slash, the original request is changed by a TrailingSlashHttpServletRequest
request wrapper that overrides getRedirectURI
, getRedirectURL
, getServletPath
and getPathInfo
.
So when you use that request in a forward
, the destination servlet doesn't see in the request the path used in the getRequestDispatcher
call because it is overridden by the wrapper. The Spring DispatcherServlet will route the request to the original controller instead of the controller mapped to the path you used in the getRequestDispatcher
call.
If you have for example a custom security filter that wants to forward an invalid or unauthorised request to a given controller using RequestDispatcher.forward()
to be processed by spring DispatcherServlet and routed to a new controller, it's not going to work well when the request ends with slash.
Before Spring 6 this was not a problem because the way to ignore trailing slash was different. Now the behaviour of the ".forward()" request depends on if the slash has been removed or not by the UrlHandlerFilter
filter.
A solution could be to test in theTrailingSlashHttpServletRequest
wrapper if the request is not a direct request, using for example:
request.getDispatcherType() == DispatcherType.REQUEST
We have made a custom filter similar to UrlHandlerFilter
that returns a request wrapper that is aware of the request.getDispatcherType()
. It only overrides the URl methods with the version without slash when the getDispatcherType is DispatcherType.REQUEST, so the calls to the forward methods an other RequestDispatcher mechanism works as expected.