Spring ResponseEntityExceptionHandler and original POST payload - spring

I'm using ResponseEntityExceptionHandler in a Spring Boot-based application, to capture errors in a single place and output a consistent error payload.
Everything works as expected.
ResponseEntityExceptionHandler has a method handleHttpMessageNotReadable that can be used to "react" on a client sending an invalid message (in my case a JSON payload).
Again, everything works as expected. If a client POST an invalid JSON document, the code in the handleHttpMessageNotReadable is executed correctly.
Now, for monitoring purposes, I would like to log the invalid JSON.
This is the code I'm using inside the handleHttpMessageNotReadable method, to get hold of the payload.
String line = null;
StringBuffer jb = new StringBuffer();
try {
BufferedReader reader = servletWebRequest.getRequest().getReader();
while ((line = reader.readLine()) != null)
jb.append(line);
} catch (IOException e) {
e.printStackTrace();
}
As expected, I get an exception because the stream has been already consumed by Jackson, for actually parsing the JSON payload:
java.lang.IllegalStateException: getInputStream() has already been called for this request
What would be a simple approach to access the original POST payload?
Would it be possible to throw a custom exception which would contain the original payload (MyException extends HttpMessageNotReadableException)?

The request body is converted from JSON into a java type by
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
If erros occur, the HttpMessageNotReadableException is thrown in the
super class org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#readJavaType
You can subclass MappingJackson2HttpMessageConverter, overwrite readJavaType(..) and try to throw your own exception or a customized HttpMessageNotReadableException there.
Most likely you will need to clone the input stream, use one to try to deserialize the JSON, and another to use in case you need to throw an exception.
This may impact performance, but if thats not a problem, you may give it a try....

Related

How to map and log exceptions but still have SpringBoot render the JSON response?

I want a global exception handler that
a) maps certain exceptions to HTTP response codes, maybe by translating e.g. NoSuchElementException to ResponseStatusException
b) adds logging depending on the exception, e.g. NullPointerException gets more verbose logging
than NoSuchElementExceptions
c) uses the SpringBoot JSON error response i.e. { timestamp: ..., status: ..., error: ... }
I read
https://www.baeldung.com/exception-handling-for-rest-with-spring and
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-exceptionhandlers
but most approaches, like #ExceptionHandler want me to provide the HTTP response body. I want to use the original SpringBoot JSON error message though which is probably made by the DefaultErrorAttributes class.
Any ideas?
I finally figured out that the obvious solution with #ExceptionHandler does indeed work but I had to add server.error.message: always to get the exception message into the response as well!
(the default is ok as it can contain sensitive information!)
Apart from that it looks like:
#RestControllerAdvice
class ExceptionHandlers {
#ExceptionHandler
fun handleAllErrors(e: Throwable, response: HttpServletResponse) {
...
response.sendError(status, message)
}
}

Rest Template pass original Message

I am building a (mostly) pass through API with RestTemplate which queries various different services. On errors and missing parameters the target APIs deliver error messages which go missing in the responses and I want to pass those through with the same HttpCode. Example:
curl -XPOST sourceapi:/...
{"type":"/errors/failed","title":"Entity Exists","details":"Entity with name \"test\" already exists","status":409}
If I do the same with a RestTemplate it throws and Exception and the message is null and it looks like this:
curl -XPOST testapi:/...
409 null
How can I pass error code as well as that object there down to the "client". (even logging it would be a start...)
I got a #ControllerAdvice class which already caches it but the message is actually just 409 null
#ExceptionHandler(value = {HttpClientErrorException.class})
public ResponseEntity<Object> clientErrorException(HttpClientErrorException ex) {
return ResponseEntity.status(ex.getStatusCode()).body(ex.getMessage());
}
Is there a way to add an ErrorParser or something while building the Template with RestTemplateBuilder?
Isn't getResponseBodyAsString() method (inherited from RestClientResponseException) what you seek?
#ExceptionHandler(value = {HttpClientErrorException.class})
public ResponseEntity<Object> clientErrorException(HttpClientErrorException ex) {
return ResponseEntity.status(ex.getStatusCode()).body(ex.getResponseBodyAsString());
}
Or perhaps getResponseBodyAsByteArray() would be a better fit.

Produce a JSONP with a ContainerRequestContext#abortWith

I have this Jersey2-based application, with a custom ContainerRequestFilter.
When the filter(ContainerRequestContext) method is called I want to do a check and, if needed, I want to be able to stop the request before entering the main logic of the application.
At the moment I'm using the ContainerRequestContext#abortWith method to block the call and return an "error" response to the client.
My application returns JSONP to the client, and if I block with abortWith the response is always a JSON.
Looking at the jersey sources I found
org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor that is responsible of the JSONP serialization.
In the abortWith flow I see it fails to find the JSONP annotation, but I don't know where it search for it.
My method has it, in fact in the "normal" scenario (without the abortWith) I see correctly the JSONP format.
I found the solution.
The ContainerRequestFilter#filter method was something like
public void filter(final ContainerRequestContext crc) throws IOException {
if (/* logic */) {
CustomObject ret = new CustomObject();
ret.error = "error message";
crc.abortWith(Response.ok(ret)).build());
}
}
JsonWithPaddingInterceptor expected a response with a JSONP annotation so I retrieve them from the ResourceInfo#resourceMethod, with something like
public void filter(final ContainerRequestContext crc) throws IOException {
if (/* logic */) {
Annotation[] as = this.resourceInfo.getResourceMethod().getAnnotations();
CustomObject ret = new CustomObject();
ret.error = "error message";
crc.abortWith(Response.ok().entity(ret, as).build());
}
}
this way the annotation is correctly found

How to handle exceptions with Swagger?

I am building some test APIs using swagger (1.5) and JAX-rs with Jersey (1.13) and I m trying to implement exception handling. For example I have the following code when receiving the results from my DB (Elasticsearch)
#POST
#Path("/category")
#ApiOperation(value="returns products")
#Produces({ "application/json" })
public Response getPostCategories(
#ApiParam(value="keyphrase, required=true) #QueryParam("keyphrase") String keyphrase,
#ApiParam(value="category) #QueryParam("category") String category,
#Context SecurityContext securityContext)
throws WebApplicationException {
SearchRequest searchRequest = new SearchRequest();
searchRequest.setKeyphrase(keyphrase);
searchRequest.setCategory(category);
SearchCategoryQuery categoryQuery = new SearchCategoryQuery();
String searchResponse = null;
try
{
searchResponse = categoryQuery.searchCategory(searchRequest);
}
catch (WebApplicationException ex)
{
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("results no found").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).build());
}
return Response.ok(searchResponse).build();
}
However, in the output swagger always prints the same response
What I need instead is to receive the error messages I specify in each exception. Any ideas?
Swagger by itself does not handle application exceptions as yet.
You will either need to create custom Exception classes (that extend java.lang.exception) or use the existing ones (like WebApplicationException that you are already using) and make the API definition throw these errors. So basically you need to use Java/J2EE/Jersey to throw proper exceptions. Swagger UI will display them for you.
Check this link for details on REST exception handling with Spring.

How to handle HttpMediaTypeNotAcceptableException by writing error content to the response body using exception handler annotation?

When a client request for a resource producing application/json content with Accept Header of application/xml. The request fails with HttpMediaTypeNotAcceptableException exception and is wrapped into error message body in the response entity object by using exception handler annotation as mentioned in below code. However, we receive HttpMediaTypeNotAcceptableException again when return values are written to the response with HttpMessageConverter. It is because it checks the producible content type for the response with the acceptable request type, but this is exactly something we are trying to communicate to the client using error message. How do I workaround this issue ? Btw, all the other exceptions are parsing fine to error message. Please advise.
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers, HttpStatus status, WebRequest request) {
// Setting the response content type to json
headers.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(headers).body(body);
}
}
A few options come to my mind. One is that your controller method produces all content types and then you throw an exception in your method if the content type is not the one you are expecting, then the exception handler can take this exception and transform it. This is the only one that works with exception handlers, as exception handlers only deal with exceptions produced in the controller method.
The other options are:
Use an interceptor (but I'm not sure if this will work, as Spring might try to resolve first the controller method rather than invoking the interceptors).
Extend RequestMappingHandlerMapping to call the exception handler if it doesn't find a suitable method. You'll probably need to override the method handleNoMatch. In there you'll need to get a reference to the list of HandlerExceptionResolver
The first one is the simplest to understand, and the latest one might be the most 'extensible', but it also requires some understanding of the internals of Spring.
Resolved by setting different content negotiation strategy FixedContentNegotiationStrategy for ExceptionHandlerExceptionResolver and HeaderContentNegotiationStrategy for RequestResponseBodyMethodProcessor.
I have been using a serialized enum-based response (enum annotated with jackson #JsonFormat(shape = Shape.OBJECT) to standardize the error messages in my exception handler class and faced the same issue when it caught with a HttpMediaTypeNotAcceptableException.
The workaround is to set the media type you expect to return directly to the builder method available in the ResponseEntity.
The below code works fine for me.
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public final ResponseEntity<ResponseMessagesEnum> handleHttpMediaTypeNotAcceptableException(
HttpMediaTypeNotAcceptableException e, HttpServletRequest request) {
logger.error("No acceptable representation found for [{}] | supported {}", request.getHeader("Accept"), e.getSupportedMediaTypes());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(ResponseMessagesEnum.EX_001);
}

Resources