Search code examples
springspring-mvcerror-handlingspring-bootstatic-resource

spring boot error page with resource handlers


tl;dr: how to enable spring's ResourceUrlEncodingFilter for spring boot Error pages?

(Question written while using spring boot 1.3.7.RELEASE and Spring Framework/MVC 4.2.4.RELEASE)

Some background: We have a fairly standard spring boot/spring webmvc project using Thymeleaf as the view layer. We have the out-of-the-box spring boot Resource Chain enabled to serve static assets.

Our thymeleaf views have standard url-encoding syntax in them such as <script th:src="@{/js/some-page.js}"></script>. This relies on Spring's org.springframework.web.servlet.resource.ResourceUrlEncodingFilter to transform the url into an appropriately-versioned url such as /v1.6/js/some-page.js.

Our error handling is done by:

  • setting server.error.whitelabel.enabled=false
  • subclassing spring boot's default BasicErrorController to override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
  • relying on our already-configured thymeleaf view resolvers to render our custom error page

The problem is: the ResourceUrlEncodingFilter isn't applying on our error pages. I assume it's a lack of the filter being registered for ERROR dispatched requests, but it's not obvious to me: a) how to customize this in spring boot; and b) why this wasn't done by default.

Update 1:

The issue seems to be with a combination of OncePerRequestFilter and the ERROR dispatcher. Namely:

  • ResouceUrlEncodingFilter does not bind to the ERROR dispatcher by default. While overriding this is messy it's not impossible, but doesn't help due to:
  • OncePerRequestFilter (parent of ResourceUrlEncodingFilter) sets an attribute on the Request indicating it's been applied so as to not re-apply. It then wraps the response object. However, when an ERROR is dispatched, the wrapped response is not used and the filter does not re-wrap due to the request attribute still being present.

Worse still, the logic for customizing boolean hasAlreadyFilteredAttribute is not overridable by request. OncePerRequestFilter's doFilter() method is final, and getAlreadyFilteredAttributeName() (the extension point) does not have access to the current request object to get the dispatcher.

I feel like I must be missing something; it seems impossible to use versioned resources on a 404 page in spring boot.

Update 2: A working but messy solution

This is the best I've been able to come up with, which still seems awfully messy:

public abstract class OncePerErrorRequestFilter extends OncePerRequestFilter {

    @Override
    protected String getAlreadyFilteredAttributeName() {
        return super.getAlreadyFilteredAttributeName() + ".ERROR";
    }

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false;
    }

}

public class ErrorPageCapableResourceUrlEncodingFilter extends OncePerErrorRequestFilter {
    // everything in here is a perfect copy-paste of ResourceUrlEncodingFilter since the internal ResourceUrlEncodingResponseWrapper is private
}

// register the error-supporting version if the whitelabel error page has been disabled ... could/should use a dedicated property for this
@Configuration
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@ConditionalOnClass(OncePerErrorRequestFilter.class)
@ConditionalOnWebApplication
@ConditionalOnEnabledResourceChain
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", havingValue="false", matchIfMissing = false)
public static class ThymeleafResourceUrlEncodingFilterErrorConfiguration {


    @Bean
    public FilterRegistrationBean errorPageResourceUrlEncodingFilterRegistration() {
        FilterRegistrationBean reg = new FilterRegistrationBean();
        reg.setFilter(new ErrorPageCapableResourceUrlEncodingFilter());
        reg.setDispatcherTypes(DispatcherType.ERROR);

        return reg;
    }
}

Better solutions?


Solution

  • This has been reported in spring-projects/spring-boot#7348 and a fix is on its way.

    It seems you've made an extensive analysis of the issue; too bad you didn't report this issue earlier. Next time, please consider raising those on the Spring Boot tracker.

    Thanks!