5

We have a spring boot app and our controller expects an XML Document element in one of our end points:

@PostMapping(value = "/api/v1/do-stuff",
        consumes = APPLICATION_XML_VALUE,
        produces = APPLICATION_XML_VALUE)
public ResponseEntity<JAXBElement<my.company.stuff.resposnse.Document>> doStuff(
        @RequestBody JAXBElement<my.company.stuff.request.Document> requestBody,
        Principal principal) throws Exception {

    // Doing some suff here and returning the result
    return stuffService.doStuff(...);
}

We have our own instance of Jaxb2Marshaller where we set the schemas for both request document and response document to be used for marshalling and un-marshalling between request and response bodies and our domain objects. When the request comes the spring boot framework will do the conversion between the request body and and the domain request document. Sometimes the request body does not pass XSD schema validation and such it does not even reach our controller.

The exception thrown is passed to our custom extension of org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler where we would like to create the most meaningful error response for our application clients:

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
            HttpHeaders headers, HttpStatus status, WebRequest request) {
        // Getting access to the request body would be very beneficial here
    }

Our issue is that sometimes the exception passed to our handler does not have enough details and we would like to be able to access the request body in order to be able to customise our response to the client. However, the input stream of the request is no longer accessible because it was already read from part of the schema validation (conversion) processing so any attempt to access it this way fails.

On the other hand whaat we are trying to achieve is very common sense and just wondering if we took the wrong approach and what would be a better design to achieve the same thing. Changing the controller to expect a plain text and validate and convert it to the request document inside the controller is not really an option.

Thank you in advance or your inputs.

Julian
  • 3,678
  • 7
  • 40
  • 72

3 Answers3

0

You can create a request scoperd entity to keep requst body,

@Component
@RequestScope
public class RequestDetails {
    private JAXBElement<my.company.stuff.request.Document> requestBody;
    //getter setter
}

Use this in the controller to set request body,

@Inject
RequestDetails requestDetails;

public ResponseEntity<JAXBElement<my.company.stuff.resposnse.Document>> doStuff(
        @RequestBody JAXBElement<my.company.stuff.request.Document> requestBody,
        Principal principal) throws Exception {
    requestDetails.setRequestBody(requestBody); //set request body
    // Doing some suff here and returning the result
    return stuffService.doStuff(...);
}

Then inject RequestDetails in ExceptionHandler and use,

Document requestBody = requestDetails.getRequestBody();
Vikas
  • 6,868
  • 4
  • 27
  • 41
  • Are you referring to `org.springframework.web.servlet.support.RequestContext`? If so it does not have a getRequestBody() method – Julian May 16 '20 at 22:02
  • Updated an answer. I hope its useful. – Vikas May 17 '20 at 05:31
  • As I said in my question when the document does not pass XSD validation it will not reach the controller at all. The exception happens before reaching the controller and once it happens it gets passed to the error handler. Inside that error handler it would be good to be able to access request body and build up a more meaningful error response. – Julian May 17 '20 at 23:18
  • Then simply create a request interceptor and set the body there. – Vikas May 18 '20 at 02:34
-1

You should be using ControllerAdvice

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    protected void handleHttpMessageNotReadableException(HttpMessageNotReadableException ex,
                                                              HttpServletResponse response,HttpServletRequest request {
      // you have full access to both request and response
    }

Anyhow if this is not suited for your case, I think you can:

@Autowired
private HttpServletRequest request;
Borislav Stoilov
  • 3,247
  • 2
  • 21
  • 46
  • 1
    I can get HttpServletRequest out of my `WebRequest` request parameter this is not a problem at all. My question is how I get the request body. Trying to get it using the request InputStream does not work as the stream is already read all bytes out of it. – Julian May 16 '20 at 22:06
-1

you can add one more param HttpServletRequest and there you will get request. Example:

@RestControllerAdvice
@Slf4j
public class InvalidRequestExceptionHandler {

    @ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<BusinessLogicValidationError> handle(InvalidRequestException exception, HttpServletRequest request) {

    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("some text");
    }
}