我有一些奇怪的错误。
我想做什么: 客户端询问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);
}
有人可以帮助我理解吗?
答案 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问题。