我有一个带有返回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 -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正在做这件事。有没有办法阻止这种情况发生?或者还有其他一些我失踪的可能性吗?
答案 0 :(得分:7)
====原始答案(解释预期行为)====
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
请求属性。代码看起来类似于下面的内容(返回带有一些&#34; 500&info&#34的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;
}
}
答案 1 :(得分:1)
使用spring-hateoas
从全局异常处理程序返回VndErrors.VndError
时,我也看到了这个问题。
根本原因是生成的响应通过writeWithMessageConverters
类中的AbstractMessageConverterMethodProcessor
方法,并且其中的逻辑最终从{中选择了 first 内容类型{1}}数组并遍历其消息转换器,寻找可以将其转换为该类型的东西。
为确保我们传递该逻辑,必须在该数组中优先使用json内容类型,以便Jackson HTTP消息转换器可以转换错误:
produces
现在,我们剩下的问题是合法回复-在您的情况下,@GetMapping(value = "/foo", produces = { MediaType.APPLICATION_JSON_UTF8_VALUE, "text/csv" } )
public ResponseEntity<String> getCsv() {
if(hasItFailed()) {
throw new IllegalArgumentException();
}
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_TYPE, "text/csv")
.body("it worked!");
}
。为了确保这些内容不会以json的内容类型结尾,您必须返回text/csv
并设置内容类型标头。 Spring的ResponseEntity
方法中的逻辑将查找并使用它。
NB:这是基于Spring Boot 2