Spring-cloud-netflix: Zuul filters exception handling

Created on 30 Jun 2016  路  17Comments  路  Source: spring-cloud/spring-cloud-netflix

Current implementation of exception handling in spring-cloud-zuul is quite ambigious. I tried to simplify it a little bit in my PR. Now I want to handle specific exceptions and return appropriate responses for them. Normally in any spring application I would create separate class annotated with ControllerAdvice and having methods with @ExceptionHandler for specific exception types. In spring-cloud-zuul exceptions get handled by BasicErrorController and none of ExceptionHandler annotated methods get executed. Is it a bug or an intenteded decision and what are the reasons?

enhancement help wanted

Most helpful comment

Understood. Thanks for the update.

All 17 comments

We're also struggling with ZuulExceptions for whatever reason of downstream exception and it's really annoying.
Will be glad to see fix, thanks!

I wouldn't expect zuul exceptions to be handled by @ExceptionHandler. I'd like to wrap my head around the problems you are seeing with zuul exceptions.

For example if timeout occurs while routing to service client gets following response from Zuul:

{
"timestamp":1479314971425,
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.zuul.exception.ZuulException",
"message":"TIMEOUT"
}

This is default message that is produced by BasicErrorController. But I want to not only have my own message format in case of such or other ZuulException I would also like to handle exceptions the way it is done in standart spring applications via @ExceptionHandler. Right now to achieve this I need to extend AbstractErrorController and the basic implementation of custom exception handling looks like that:

@RestController
public class ZuulErrorController extends AbstractErrorController {

    @Value("${error.path:/error}")
    private String errorPath;

    public ZuulErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    @Override
    public String getErrorPath() {
        return errorPath;
    }

    @RequestMapping(value = "${error.path:/error}", produces = "application/json;charset=UTF-8")
    public ResponseEntity error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        final String errorMessage = getErrorMessage(request);
        return ResponseEntity.status(status).body(generateErrorResponse(UNKNOWN_ERROR, errorMessage));
    }

    private String getErrorMessage(HttpServletRequest request) {
        final Throwable exc = (Throwable) request.getAttribute("javax.servlet.error.exception");
        return exc != null ? exc.getMessage() : "Unexpected error occurred";
    }
}

Please advice how exception handling in Zuul can be done the right way.
Thanks.

When you say "spring application" I think you mean "spring mvc application". Zuul isn't a standard spring mvc application.

What does the response look like with your error controller?

Okay, I understand that. Still it would be nice to have something like @ExceptionHandler that gives an ability to code custom logic of exception handling in Zuul.

With my error controller I produce response that is common for our client:

{
"errorCode": -1, 
"errorMessage": "Forwarding error"
}

Understood. Thanks for the update.

@spencergibb I have a similar use case my filter is calling an authentication method but if this authentication method throws any exception is there a way for me handle it in filter and then respond with a custom json format? BTW, I am currently using Camden.SR4 release. If any example will really help me. Thanks in advance.

I have no example.

@spencergibb This is what I did and could you please validate. Thanks in advance.

  1. I have upgraded to Dalston release
  2. Now any exception throwing from pre-filters are caught in error filter. From there it is redirected to /error resource. Thanks to @Aloren.
  3. I have an error controller with the /error mapping and rethrowing the exception in GatewayApplication
  4. I have a @ControllerAdvicer which have all handlers for all types of exceptions. This advicer will be part of exception handling module and this module will be used in Services side as well apart from filters.

@stiyyagura We've solved a similar use case. I suggest that you open a StackOverflow question instead of taking over this thread, perhaps I can help you there. Put the link to your question here for reference.

@asarkar thank you for response. This is solved and old issue.

@stiyyagura Would you mind explaining what did you end up doing, so if someone stumbles upon this thread later, they'll have something to go on.

@asarkar I have explained my approach in the above. I am using error filter to handle the gateway errors and forwarding to /error path.

@stiyyagura
Error filter -> Error Controller -> ControllerAdvice

Is that right?

@spencergibb

When you say "spring application" I think you mean "spring mvc application". Zuul isn't a standard spring mvc application.

I've just looked into SendErrorFilter and it has following code inside run method:

RequestDispatcher dispatcher = request.getRequestDispatcher(
        this.errorPath);
        if (dispatcher != null) {
    ctx.set(SEND_ERROR_FILTER_RAN, true);
    if (!ctx.getResponse().isCommitted()) {
        dispatcher.forward(request, ctx.getResponse());
    }
}

It redirects rendering of exception to ErrorController of spring-boot. Isn't it a part of a spring-mvc application?
I suppose, that SendErrorFilter should be changed. It should not to redirect exception handling to spring-mvc part of an application. Please have a look at ErrorFilter in netflix. It handles exception and after it post filter is run that actually sends response to client.

@asarkar Yes it is ErrorFilter --> ErrorController --> ControllerAdvice.
And I give here more details:

  • First add a filter of type error and let it be run before the SendErrorFilter in zuul itself.
  • Make sure to remove the key associated with the exception from the RequestContext to prevent the SendErrorFilter from executing.
  • Use RequestDispatcher to forward the request to the ErrorController.
  • Follow the same structure as @Aloren provided above for the rest controller (you have to extend AbstractErrorController), and re-throw the exception again (add it in the step of executing your new error filter with (key, exception), get it from the RequestContext in your controller).

This module has entered maintenance mode. This means that the Spring Cloud team will no longer be adding new features to the module. We will fix blocker bugs and security issues, and we will also consider and review small pull requests from the community.

Was this page helpful?
0 / 5 - 0 ratings