Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExceptionHandlerExceptionResolver should support exceptions from any handler #22619

Closed
vghero opened this issue Mar 20, 2019 · 12 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@vghero
Copy link

vghero commented Mar 20, 2019

Affects: Spring 5.0.9

I have a Spring Boot service where custom JSON error responses are returned for cases like 400, 404, 415, 500 etc. This works great by using @ExceptionHandler.

Recently we added static resources to the mix. But when accessing one of them that does not exist, the default Spring 404 JSON body is returned instead of ours. As it turns out, ExceptionHandlerExceptionResolver and superclasses are refusing to resolve due to the fact that the handler method is not instance of HandlerMethod but ResourceHttpRequestHandler and error handling ends up somewhere in dispatcher servlet with the "default" 404 response.

I've managed to re-use ExceptionHandlerExceptionResolver also for static resources not found, but it is quite hackish and does not feel right - see below. Are there any plans to add support for this to ExceptionHandlerExceptionResolver? Or did I overlook something?

@EnableWebMvc
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
                .addResourceHandler("/apidocs/**")
                .addResourceLocations("classpath:/apidocs/")
                .resourceChain(true)
                .addResolver(new ThrowingPathResourceResolver());
    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

        ExceptionHandlerExceptionResolver defaultResolver = (ExceptionHandlerExceptionResolver) resolvers.stream()
                .filter(resolver -> resolver instanceof ExceptionHandlerExceptionResolver).findAny()
                .orElseThrow(() -> new IllegalStateException(
                        "No registered " + ExceptionHandlerExceptionResolver.class.getSimpleName() + " found."));

        ExceptionHandlerExceptionResolver resolver = new ResourceExceptionHandlerExceptionResolver();
        resolver.setApplicationContext(defaultResolver.getApplicationContext());
        resolver.setContentNegotiationManager(defaultResolver.getContentNegotiationManager());
        resolver.setCustomArgumentResolvers(defaultResolver.getCustomArgumentResolvers());
        resolver.setCustomReturnValueHandlers(defaultResolver.getCustomReturnValueHandlers());

        resolver.afterPropertiesSet();

        resolver.setReturnValueHandlers(defaultResolver.getReturnValueHandlers() == null ? null
                : defaultResolver.getReturnValueHandlers().getHandlers());
        resolver.setArgumentResolvers(defaultResolver.getArgumentResolvers() == null ? null
                : defaultResolver.getArgumentResolvers().getResolvers());

        resolvers.add(resolver);
    }

    private class ResourceExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver {

        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                Exception ex) {

            if (handler instanceof ResourceHttpRequestHandler) {
                return doResolveException(request, response, (HandlerMethod) null, ex);
            }
            return null;
        }
    }

    public class ResourceNotFoundException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public ResourceNotFoundException(String message) {
            super(message);
        }
    }

    private class ThrowingPathResourceResolver extends PathResourceResolver {

        @Override
        public Resource resolveResource(HttpServletRequest request, String requestPath,
                List<? extends Resource> locations, ResourceResolverChain chain) {

            Resource resource = super.resolveResource(request, requestPath, locations, chain);
            if (resource == null) {
                throw new ResourceNotFoundException(
                        "The resource " + request.getRequestURL() + " could not be found.");
            }

            return resource;
        }
    }
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 20, 2019
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Mar 21, 2019
@rstoyanchev
Copy link
Contributor

Originally @ExceptionHandler was meant to handle exceptions from handler methods in the same controller class or pass on to other resolvers to try. Later on @ControllerAdvice made it possible to handle exceptions from any handler.

I think we could use the mappedHandlers and mappedHandlerClasses from the base class to determine if the request should be handled in case the handler is not a HandlerMethod. The doResolveHandlerMethodException method already accepts null as the HandlerMethod so if not a HandlerMethod just pass null.

@rstoyanchev rstoyanchev changed the title ExceptionHandlerExceptionResolver should support exceptions from ResourceHandlers ExceptionHandlerExceptionResolver should support exceptions from ResourceHttpRequestHandler Mar 27, 2019
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 27, 2019
@rstoyanchev rstoyanchev added this to the 5.x Backlog milestone Mar 27, 2019
@joshlong
Copy link
Member

joshlong commented Mar 28, 2019

It would be nice to be able to centralize error handing and also to be able to contribute error handling logic, eg, in a Spring Boot autoconfig. As it is I can’t reuse @ControllerAdvice for exceptions thrown from HandlerFunction's. I want a way for spring Boot autoconfig to be able to contribute advice exception handling logic without requiring the client to install it into the filter chain. As it is I can use @ExceptionHandler / controller advice w Spring MVC-style @RestController's, but I can’t share that same with HandlerFunction's.

@joshlong
Copy link
Member

joshlong commented Mar 28, 2019

As we are now supporting the functional reactive webflux dsl in spring MVC it would also be nice to share @ExceptionHandler logic across webflux and MVC, across .fn DSL and @Controller's

@rstoyanchev rstoyanchev changed the title ExceptionHandlerExceptionResolver should support exceptions from ResourceHttpRequestHandler ExceptionHandlerExceptionResolver should support exceptions from any handler Apr 3, 2019
@rstoyanchev
Copy link
Contributor

@joshlong indeed this is another use case for this. I've broadened the title accordingly.

@adaykin
Copy link

adaykin commented Jan 3, 2020

I think one thing that would be nice would be an easy way to configure on setup a method that gets called after an exception gets handled. Currently I'm using controlleradvice to handle exceptions, but this gets missed when an exception occurs from a filter. I want a way to just easily log all exceptions that are uncaught.

@exceptionplayer
Copy link

I have also encountered this problem. I can handle all exceptions in a custom ExceptionFilter just like Spring Security did with ExceptionTranslationFilter.
For now, all the exception handlers are only suitable for exceptions occurred after filters, i agree with @adaykin that it would be better for spring to provide a centralized exception handler also suitable for exceptions in filters.

@grimsa
Copy link
Contributor

grimsa commented Jul 15, 2020

Ran into the same issue.

We use Atlassian's swagger-request-validator for validating requests against OpenAPI spec. It's implemented as a servlet filter that throws an exception in case the request does not match OpenAPI spec.

A centralized @ControllerAdvice class with @ExceptionHandler method then translates this validation exception into a nicely formatted HTTP response.

This works perfectly when request URL is handled by a Controller (i.e. HandlerMethod), but if a client requests the incorrect URL (i.e. no matching HandlerMethod, and handler is ResourceHttpRequestHandler), then our exception translating @ExceptionHandler method no longer works.

@rstoyanchev rstoyanchev modified the milestones: 5.x Backlog, 5.3 M2 Jul 20, 2020
@rstoyanchev rstoyanchev self-assigned this Jul 20, 2020
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jul 27, 2020

@adaykin, @medusar, @grimsa, the original request is for @ExceptionHandler methods to handle exceptions from Spring MVC handlers other than controllers with @RequestMapping methods. That's still within Spring MVC though. The handling of exceptions from filters is a separate discussion and not in scope for this issue.

@rstoyanchev
Copy link
Contributor

This is now supported through the mappedHandlers or mappedHandlerClasses properties which can be set to indicate to which handlers exceptions should apply.

@haupv1m
Copy link

haupv1m commented Apr 5, 2021

@rstoyanchev Sorry for requesting reopen. I encountered problem when setting mappedHandlerClasses and mappedHandlers for ExceptionHandlerExceptionResolver.

  • First, I setup mapped for handle Functional Controller (HandlerFunction class)
  • This leads to ExceptionHandlerExceptionResolver cannot resolve null handler case (ex: NotFoundHandlerException). Also, to handle exception in Filter, I implement a highest order Filter (called ExceptionRecoveryFilter) to capture all inner Filter's exceptions and resolve it in the same way as Security Spring did with ExceptionTranslationFilter. Because of setting mappedHandlerClasses, resolver cannot resolve exception from ExceptionRecoveryFilter filter.

Is there any clever way to resolve this issue? Currently, I have to work around in the same way @vghero do.

@rstoyanchev
Copy link
Contributor

@haupv1m can you create a separate issue?

@haupv1m
Copy link

haupv1m commented Apr 7, 2021

@rstoyanchev I already create new issue #26772

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

9 participants