Reputation: 26567
In a Spring application, I have an endpoint which normally returns an image (produces = MediaType.IMAGE_PNG_VALUE
).
I also have @ExceptionHandler
functions to handle various functions.
I'm trying to find a way to determine, from within the @ExceptionHandler
, if the client will accept text/plain
or text/json
so in the event of an error I can return back one of those, or omit it if they are only expecting image/png
.
How can I determine what acceptable content types I can return for a given request?
Upvotes: 4
Views: 1566
Reputation: 26567
This is the answer I came up with. It's similar to YoungSpice's, but it is a little more flexible and uses MediaType directly (which means it'll handle wildcard types like text/*
and the like):
private ResponseEntity<String> buildResponse(WebRequest request, HttpStatus status, String message) {
HttpHeaders httpHeader = new HttpHeaders();
List<MediaType> acceptHeader =
MediaType.parseMediaTypes(Arrays.asList(request.getHeaderValues(HttpHeaders.ACCEPT)));
if (acceptHeader.stream().anyMatch(mediaType -> mediaType.isCompatibleWith(MediaType.APPLICATION_JSON))) {
httpHeader.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>("{ \"error\": \"" + message + "\" }", httpHeader, status);
} else if (acceptHeader.stream().anyMatch(mediaType -> mediaType.isCompatibleWith(MediaType.TEXT_PLAIN))) {
httpHeader.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<>(message, httpHeader, status);
} else {
return ResponseEntity.status(status).body(null);
}
}
Basically, it uses MediaType.parseMediaTypes()
to parse the Accept
header, then I stream through them and use the mediaType.isCompatibleWith()
function to check if my target is acceptable. This will let it handle if the header has something like application/*
instead of application/json
directly.
It also seems like if Accept
isn't explicitly provided in the request, there is an implied */*
, which seems to work as intended.
Upvotes: 3
Reputation: 2128
You can access the request to inspect headers and return an appropriate response. It is standard Content Negotiation.
Here's an example:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = {RuntimeException.class})
protected ResponseEntity<Object> handleMyException(RuntimeException ex, WebRequest request) {
List<String> acceptableMimeTypes = Arrays.asList(request.getHeaderValues(HttpHeaders.ACCEPT));
if (acceptableMimeTypes.contains(MediaType.TEXT_PLAIN_VALUE)) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
.body("hello");
}
throw ex;
}
}
There are some arguments that spring-mvc
can automagically inject into controller
methods, and WebRequest
(which is spring
's representation of an http
request) is one of those. If the client has sent an Accept : text/plain
header with the request, the above example returns the string hello
if there's a RuntimeException
. If there's no exception, this logic won't get triggered at all, so the endpoint will just return whatever it normally returns. You can read more about @ControllerAdvice
and @ExceptionHandler
here.
Of course, be sure to think about the exact exception types you want to handle, and semantically appropriate status codes to return so that the clients know how to correctly interpret the response.
Upvotes: 5