Reputation: 10813
I have a Rest web service based on Spring MVC. I use a @RestControllerAdvice
to handle exceptions thrown from my @Controller
s.
An example of call is as below
@GetMapping(value = "/{id}/{name:.+}", produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE,
MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<byte[]> getSomething(
@PathVariable("id") String id, @PathVariable("name") String name) throws customException;
A basic method that can produce 3 media types : APPLICATION_OCTET_STREAM_VALUE
, APPLICATION_XML_VALUE
and APPLICATION_JSON_VALUE
and throws a customException
The definition of my @RestControllerAdvice
is as the following :
@ExceptionHandler({ CustomException.class })
public ResponseEntity<Object> handleException(CustomException e) {
ErrorDto err = errorMapper.map(e);
Enumeration<String> en = httpServletRequest.getHeaders(HttpHeaders.ACCEPT);
while (en.hasMoreElements()) {
String list = en.nextElement();
StringTokenizer st = new StringTokenizer(list, ",");
while (st.hasMoreTokens()) {
String acc = st.nextToken();
MediaType contentTypeHeader = MediaType.valueOf(acc);
if (MediaType.APPLICATION_XML.includes(contentTypeHeader)) {
JAXBElement<ErrorDto> ret = new ObjectFactory().createError(err);
return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED)
.contentType(MediaType.APPLICATION_XML).body(ret);
} else if (MediaType.APPLICATION_JSON.includes(contentTypeHeader)) {
return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED)
.contentType(MediaType.APPLICATION_JSON).body(err);
}
}
}
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(null);
}
According to the request accept header, the @ExceptionHandler
returns a null body and a NOT_ACCEPTABLE
http response status if no accept is set, or a object of type ErrorDto
if accept is of type APPLICATION_JSON
or a JaxbElement
of ErrorDto
is accept is of type APPLICATION_XML
Please note that I specify the content type of the response when it contains a body.
My problem is when a client make a call with multiple accept headers, Spring tries to pick up an HttpMessageConverter
according to the accept header and not to the response content type. Below an example :
When the client call a method that throws an exception (and then returns an errorDto) with multiple Accept headers as below :
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM_VALUE);
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML_VALUE);
HttpEntity<?> entity = new HttpEntity<>(body, headers);
restTemplate.exchange(uriComponents.encode().toUri(), httpMethod, entity, Class);
Spring does not return a response with the error in the XML format as expected. It looks for a Octet-Stream <-> JaxbElement
converter that does not exist and does not try to find the converter according to the response content type.
I'm using :
Upvotes: 3
Views: 1793
Reputation: 935
Spring has this wrong. According to RFC2616, 406 Not Acceptable:
Note: HTTP/1.1 servers are allowed to return responses which are not acceptable according to the accept headers sent in the request. In some cases, this may even be preferable to sending a 406 response. User agents are encouraged to inspect the headers of an incoming response to determine if it is acceptable.
In my experience, error handling for REST services is one of the cases where it is preferable to force the response to something simple, like text/plain, to get an meaningful error message to the client.
Spring's over-opinionated behavior here has the effect of masking the primary error that occurs with a 406 response. So the RFC comments above apply equally to all 4xx and 5xx responses. If you can't match an Accept'ed content type, tho, you probably should not be sending any responses in the 1xx, 2xx or 3xx ranges.
Upvotes: 0