简体   繁体   中英

Spring HttpMessageConverter according to @ExceptionHandler response content type

I have a Rest web service based on Spring MVC . I use a @RestControllerAdvice to handle exceptions thrown from my @Controller s.


Controller

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


Exception handler

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.


Problem

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.

How can I force Spring to use a converter according to the response content type ?


I'm using :

  • Spring Boot 1.4.0.RELEASE

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM