我有一个带有返回text / csv的方法的控制器。对于正常的成功案例,这很好用,但是如果引发异常,并且我有Accept: text/csv
的标头,则会收到406响应。例如:
@RequestMapping(value = "/foo", method = RequestMethod.GET, produces = "text/csv")
public String getCsv() {
throw new IllegalArgumentException();
}
这是在完全原始的Spring Boot应用程序(Maven项目,导入
spring-boot-starter-web-services
)中,该组件仅由具有上述方法的控制器组成。我认为原因是框架正在将异常转换为JSON错误响应。如果删除
produces
属性并发送Accept: */*
,我将获得该异常的JSON表示形式。显然,JSON不是text/csv
,因此是406(不可接受)响应。这是显示问题的curl请求/响应的示例:
curl -v http://localhost:8080/foo -H 'accept: text/csv'
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /foo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> accept: text/csv
>
< HTTP/1.1 406
< X-Application-Context: application
< Content-Length: 0
< Date: Sat, 16 Dec 2017 23:04:05 GMT
<
* Connection #0 to host localhost left intact
但是,有趣的是,如果我在Spring应用程序中查看
/trace
端点,则会看到一些不同的东西:{
"timestamp": 1513465445542,
"info": {
"method": "GET",
"path": "/foo",
"headers": {
"request": {
"host": "localhost:8080",
"user-agent": "curl/7.47.0",
"accept": "text/csv"
},
"response": {
"X-Application-Context": "application",
"status": "500"
}
},
"timeTaken": "1"
}
}
因此,Spring认为它返回的是500,但是当它卷曲时,它是406。如果我从PostMan发送请求,我会看到完全一样的东西。
我不确定是什么导致了从500到406的变化。我认为它不是客户端,所以我最好的猜测是Tomcat正在这样做。有什么办法可以阻止这种情况的发生?还是我想念其他的可能性?
最佳答案
====原始答案(解释预期的行为)====Accept
标头指定客户端希望服务器响应的格式类型。对此的任何差异都将导致HTTP 406 - Not Acceptable
错误。但是,此错误并不表示该操作失败,而是指示对于指定格式的客户端期望失败。
在您的情况下,Accept
标头携带text/csv
,但服务器响应application/json
,因此406
错误,因为存在明显的不匹配。
要更正此行为,无需在服务器/弹簧端进行任何更改。相反,客户端应开始发送Accept
标头,该标头将带有值application/json,text/csv
。这将确保客户端期望两种格式并在有效/错误响应的情况下支持它们。
有关更多详细信息,请参考here。
编辑 2017年12月22日
Spring团队here将观察到的行为确认为错误。尚无已知的解决方法。
编辑 2018年1月4日
如Spring JIRA comments中所述,它是一种解决方法,我们需要删除HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
中的@RestControllerAdvice
请求属性。代码如下所示(返回500并带有一些“信息”-对象的序列化版本也将返回)。
休息控制器建议
@RestControllerAdvice
public class ExampleControllerAdvice {
@ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorResponse> handleException(HttpServletRequest request, Exception e) {
ErrorResponse response = new ErrorResponse();
response.setErrorMsg("Server error " + e); // or whatever you want
response.setErrorCode("ERROR007"); // or whatever you want
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<ErrorResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
ErrorResponse对象
public class ErrorResponse {
private String errorCode;
private String errorMsg;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
编辑 2019年6月27日
现在在Spring Framework中是fixed。 Spring在处理异常之前会自动删除请求属性
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
。