简体   繁体   中英

How can I override Dropwizard's default resource exception handling?

Suppose I've got an endpoint in Dropwizard, say

@GET
public Response foo() { throw new NullPointerException(); }

When I hit this endpoint it logs the exception and everything, which is great! I love it. What I love less is that it returns a big status object to the user with status: ERROR (which is fine) as well as a gigantic stack trace, which I'm less excited about.

Obviously it's best to catch and deal with exceptions on my own, but from time to time they're going to slip through. Writing a try catch block around the entire resource every time is fine, but (a) it's cumbersome, and (b) I always prefer automated solutions to "you have to remember" solutions.

So what I would like is something that does the following:

  1. Logs the stack trace (I use slf4j but I assume it would work for whatever)
  2. Returns a general purpose error response, which does not expose potentially privileged information about my server!

I feel like there must be a built-in way to do this -- it already handles exceptions in a relatively nice way -- but searching the docs hasn't turned up anything. Is there a good solution for this?

As alluded to by reek in the comments, the answer is an ExceptionMapper . You'll need a class like this:

@Provider
public class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> {
    @Override
    public Response toResponse(RuntimeException runtime) {
        // ...
    }
}

You can do whatever logging or etc. you like in the toResponse method, and the return value is what is actually sent up to the requester. This way you have complete control, and should set up sane defaults -- remember this is for errors that slip through, not for errors you actually expect to see! This is also a good time to set up different behaviors depending on what kind of exceptions you're getting.

To actually make this do anything, simply insert the following line (or similar) in the run method of your main dropwizard application:

environment.jersey().register(new RuntimeExceptionMapper());

where environment is the Environment parameter to the Application's run method. Now when you have an uncaught RuntimeException somewhere, this will trigger, rather than whatever dropwizard was doing before.

NB: this is still not an excuse not to catch and deal with your exceptions carefully!

Add the following to your yaml file. Note that it will remove all the default exception mappers that dropwizard adds.

server: registerDefaultExceptionMappers: false

Write a custom exception mapper as below:

public class CustomExceptionMapper implements ExceptionMapper<RuntimeException> {
    @Override
    public Response toResponse(RuntimeException runtime) {
        // ...
    }
}

Then register the exception mapper in jersey: environment.jersey().register(new CustomExceptionMapper());

Already mentioned this under the comments, but then thought I would give it a try with a use case.

Would suggest you to start differentiating the Exception that you would be throwing. Use custom exception for the failures you know and throw those with pretty logging. At the same RuntimeException should actually be fixed. Anyhow if you don't want to display stack trace to the end user you can probably catch a generic exception, log the details and customize the Response and entity accordingly.

You can define a

public class ErrorResponse { 

    private int code;
    private String message;

    public ErrorResponse() {
    }

    public ErrorResponse(int code, String message) {
        this.code = code;
        this.message = message;
    }
    ... setters and getters
}

and then within you resource code you can modify the method as -

@GET
public Response foo() { 
   try {
       ...
       return Response.status(HttpStatus.SC_OK).entity(response).build();
   } catch (CustomBadRequestException ce) {
       log.error(ce.printStackTrace());
       return Response.status(HttpStatus.SC_BAD_REQUEST).entity(new ErrorResponse(HttpStatus.SC_BAD_REQUEST, ce.getMessage())).build();
   } catch (Exception e) {
       log.error(e.printStackTrace(e));
       return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).entity(new ErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage())).build();
   }      
}

This article details Checked and Unchecked Exceptions implementation for Jersey with customized ExceptionMapper :

https://www.codepedia.org/ama/error-handling-in-rest-api-with-jersey/

Official Dropwizard documentation also covers a simpler approach, just catching using WebApplicationException :

@GET
@Path("/{collection}")
public Saying reduceCols(@PathParam("collection") String collection) {
    if (!collectionMap.containsKey(collection)) {
        final String msg = String.format("Collection %s does not exist", collection);
        throw new WebApplicationException(msg, Status.NOT_FOUND)
    }

    // ...
}

https://www.dropwizard.io/en/stable/manual/core.html#responses

It worked for me by simply registering the custom exception mapper created in the run method of the main class.

environment.jersey().register(new CustomExceptionMapper());

where CustomExceptionMapper can implement ExceptionMapper class like this

public class CustomExceptionMapperimplements ExceptionMapper<Exception>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM