Spring @ExceptionHandler返回HTTP 406

时间:2017-06-01 13:35:32

标签: java spring rest spring-mvc exception-handling

我有一些奇怪的错误。

我想做什么: 客户端询问GET:/ invoices / invoiceNumber,标题为Accept:application / pdf,我想返回PDF文件。如果客户端忘记了标题,我返回HTTP 406。

返回PDF字节的方法抛出由Spring ExceptionHandler处理的DocumentNotFoundException,并且应该返回404,但它没有。而不是那样,我有406和服务器日志:

 2017-06-01 15:14:03.844  WARN 2272 --- [qtp245298614-13] o.e.jetty.server.handler.ErrorHandler    : Error page loop /error

当Spring Security返回HTTP 401时,会发生同样的魔术。

所以我认为问题是客户端接受application/pdf但Spring ExceptionHandler返回application/json,因此jetty调度程序用406覆盖404 :(

我的代码:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Invoice not found")
@ExceptionHandler(DocumentNotFoundException.class)
public void handleException() {
    //impl not needed
}

@GetMapping(value = "invoices/**", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<byte[]> getInvoicePdf(HttpServletRequest request) {
    String invoiceNumber = extractInvoiceNumber(request);
    final byte[] invoicePdf = invoiceService.getInvoicePdf(invoiceNumber);
    return new ResponseEntity<>(invoicePdf, buildPdfFileHeader(invoiceNumber), HttpStatus.OK);

}

@GetMapping(value = "invoices/**")
public ResponseEntity getInvoiceOther() {
    return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
}

有人可以帮助我理解吗?

2 个答案:

答案 0 :(得分:2)

问题是Spring尝试将错误响应转换为application/pdf,但未能找到支持转换为PDF的合适HttpMessageConverter

最简单的解决方案是手动创建错误响应:

@ExceptionHandler(DocumentNotFoundException.class)
public ResponseEntity<?> handleException(DocumentNotFoundException e) {

    return ResponseEntity
        .status(HttpStatus.NOT_FOUND)
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .body("{\"error\": \"Invoice not found\"}");
}

这会绕过邮件转换并生成HTTP 404响应代码。

答案 1 :(得分:0)

为了解决臭名昭著的406问题,这是我今天(2020年1月20日)在Spring Boot 2.2.4中发布修复之前采用的解决方案。

我扩展了ResponseEntity<>并强制其始终使用JSON内容类型。然后,在@Rest/ControllerAdvice的权限下,在全局异常处理程序中返回此新实例化,如下所示:

import org.jetbrains.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;

import static org.springframework.http.HttpHeaders.CONTENT_TYPE;

public class JsonResponseEntity<T> extends ResponseEntity<T>
{
    // You need an inner class or else you will run into super() issues
    private static class Helper
    {
        private static MultiValueMap<String, String> headerHelper( @Nullable MultiValueMap<String, String> headers )
        {
            if ( headers == null )
            {
                headers = new HttpHeaders();
            }

            // The following is a generic version of: getHeaders().setContentType( MediaType.APPLICATION_JSON ); // NOSONAR
            headers.set( CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8.toString() );
            return headers;
        }
    }

    public JsonResponseEntity( HttpStatus status )
    {
        this(null, Helper.headerHelper( null ), status);
    }

    public JsonResponseEntity( T body, HttpStatus status )
    {
        this(body, Helper.headerHelper( null ), status);
    }

    public JsonResponseEntity( MultiValueMap<String, String> headers, HttpStatus status )
    {
        super( Helper.headerHelper( headers ), status );
    }

    public JsonResponseEntity( T body, MultiValueMap<String, String> headers, HttpStatus status )
    {
        super( body, Helper.headerHelper( headers ), status );
    }
}

然后在期望处理程序中可以返回:

@ExceptionHandler(MyException.class)
public ResponseEntity<ErrorResponse> handleMyException(MyException e) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, e.getLocalizedMessage()); // ErrorResponse is just a POJO

    // You can do this:
    // return ResponseEntity.status( 404 ).contentType( MediaType.APPLICATION_JSON ).body( errorResponse );

    // or this:
    return new JsonResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}

通过在ResponseEntity级别上将内容类型强制为JSON,应该不会出现406问题。