Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the @RequestBody in an @ExceptionHandler (Spring REST)

I am using Spring Boot 1.4.1 which includes spring-web-4.3.3. I have a class annotated with @ControllerAdvice and methods annotated with @ExceptionHandler to handle exceptions thrown by the service code. When handling these exceptions, I would like to log the @RequestBody that was part of the request for PUT and POST operations so I can see the request body that caused the problem which in my case is crucial for diagnosis.

Per Spring Docs the method signature for @ExceptionHandler methods can include various things including the HttpServletRequest. The request body can normally be obtained from here via getInputStream() or getReader(), but if my controller methods parse the request body like "@RequestBody Foo fooBody" as all of mine do, the HttpServletRequest's input stream or reader is already closed by the time my exception handler method is called. Essentially the request body has already been read by Spring, similar to the issue described here. It is a common problem working with servlets that the request body can only be read once.

Unfortunately @RequestBody is not one of the options available for the exception handler method, if it were then I could use that.

I can add an InputStream to the exception handler method, but that ends up being the same thing as the HttpServletRequest's InputStream and so has the same issue.

I also tried getting the current request with ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest() which is another trick for getting the current request, but this ends up being the same HttpServletRequest that Spring passes into the exception handler method and so has the same problem.

I have read about a few solutions like this and this that involve inserting a custom request wrapper in the filter chain that will read the contents of the request and cache them so they can be read more than once. I don't like this solution because I don't want to interrupt the entire filter/request/response chain (and potentially introduce performance or stability problems) just to implement logging, and if I have any large requests such as uploaded documents (which I do), I don't want to cache that in memory. Besides, Spring probably has the @RequestBody cached somewhere already if I could only find it.

Incidentally many solutions recommend using the ContentCachingRequestWrapper Spring class but in my experience this does not work. Aside from not being documented, looking at its source code it looks like it only caches the parameters, but not the request body. Trying to get the request body from this class always results in an empty string.

So I am looking for any other options that I may have missed. thanks for reading.

like image 606
Uncle Long Hair Avatar asked Apr 19 '17 17:04

Uncle Long Hair


1 Answers

You can reference the request body object to a request-scoped bean. And then inject that request-scoped bean in your exception handler to retrieve the request body (or other request-context beans that you wish to reference).

// @Component // @Scope("request") @ManagedBean @RequestScope public class RequestContext {     // fields, getters, and setters for request-scoped beans }  @RestController @RequestMapping("/api/v1/persons") public class PersonController {      @Inject     private RequestContext requestContext;      @Inject     private PersonService personService;      @PostMapping     public Person savePerson(@RequestBody Person person) throws PersonServiceException {          requestContext.setRequestBody(person);          return personService.save(person);     }  }  @ControllerAdvice public class ExceptionMapper {      @Inject     private RequestContext requestContext;      @ExceptionHandler(PersonServiceException.class)     protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) {          Object requestBody = requestContext.getRequestBody();          // ...          return responseEntity;     } } 
like image 177
Warren Nocos Avatar answered Sep 28 '22 06:09

Warren Nocos