我有一个带有返回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

07-25 22:33
查看更多