【发布时间】:2016-09-22 10:38:57
【问题描述】:
我正在创建一个 REST API,并且发现在某些情况下很难选择正确的 HTTP 状态代码来返回。
假设我期望某个值,但当它不存在时,我无法执行某个任务并返回错误。由于缺少值,服务器无法处理请求,但发送请求的是客户端,格式正确但不完整。最好返回4xx 或5xx 错误?
【问题讨论】:
我正在创建一个 REST API,并且发现在某些情况下很难选择正确的 HTTP 状态代码来返回。
假设我期望某个值,但当它不存在时,我无法执行某个任务并返回错误。由于缺少值,服务器无法处理请求,但发送请求的是客户端,格式正确但不完整。最好返回4xx 或5xx 错误?
【问题讨论】:
将哪个 HTTP 状态代码发送给客户端由您决定,但您确实应该遵守标准。 RFC 7231 是 HTTP/1.1 协议的内容和语义的当前参考。在 HTTP 协议之上创建 API 时必读。
4xx vs 5xx 状态码使用4xx 状态码表示客户端错误,使用5xx 状态码表示服务器错误:
4xx(客户端错误)类状态码表示客户端 似乎犯了错误。除了响应HEAD请求时, 服务器应该发送一个包含解释的表示 错误情况,以及它是暂时的还是永久的 健康)状况。这些状态码适用于任何请求方法。 用户代理应该向用户显示任何包含的表示。
5xx(服务器错误)类状态码表示服务器 意识到它有错误或无法执行 请求的方法。除了响应HEAD请求时, 服务器应该发送一个包含解释的表示 错误情况,以及它是暂时的还是永久的 健康)状况。用户代理应该显示任何包含的表示 给用户。这些响应代码适用于任何请求 方法。
对于您在问题中提到的情况,您可以使用400 或422(来自WebDAV,一个HTTP 扩展):
400(错误请求)状态码表示服务器不能或 由于某些被认为是 客户端错误(例如,格式错误的请求语法、无效请求 消息框架或欺骗性请求路由)。
11.2. 422 Unprocessable Entity
422(不可处理实体)状态码表示服务器 理解请求实体的内容类型(因此415(不支持的媒体类型)状态码不合适),并且 请求实体的语法是正确的(因此是400(Bad Request) 状态码不合适)但无法处理包含的 指示。例如,如果 XML 请求正文包含格式正确(即语法正确),但是 语义错误的 XML 指令。
连同状态代码,确保您发送包含响应负载中错误情况说明的表示(例如 JSON 或 XML)。查看RFC 7807,它描述了 HTTP API 问题详细信息的标准。
有关更多详细信息,请查看来自 Racksburg 的 decision chart:
状态码大致分为三个类别:
从这里开始:
选择2xx 和3xx 状态码:
选择4xx 状态码:
选择5xx 状态码:
【讨论】:
您想向客户发送什么响应完全取决于您。
但是在发送响应时,您必须注意它应该是通用的并且清楚地说明导致请求失败的事件。
设计API就像设计客户端和服务器之间的协议。 在遵循该协议或 API 规范之前,服务器必须为客户端提供服务。它应该在 Api 规范文档中明确说明。
在您的上述情况下,由于服务器使用请求参数接受某些值并且客户端没有发送它,这是客户端的错误,因为客户端不同意在发送请求之前提供的 api 规范。
在这种情况下,服务器应该返回 HttpResponseStatus.BAD_REQUEST,在代码中等于 400。
提示: 我建议您不仅应该返回错误代码,还应该在发生任何错误时在响应中提供一些错误消息。
例如
response : { errorCode:4xx, errorMessage:"Some thing went wrong"}
【讨论】:
TL;DR Stick to standards!,但在某些情况下可以放宽。
我认为 4xx 与 5xx 取决于服务器与客户端的关系。如果您正在构建公共 API,或者您不确定如何使用 API,我同意接受的答案。事实上,我认为它应该是构建 API 时的默认选择。
但是,区分 4xx 和 5xx(具体来说是 400 和 500)会产生一定的成本。在某些情况下,成本可能不合理。假设您正在构建一个 API,并且您可以完全控制客户端。 API 和客户端都属于同一个应用程序,它们将一起发布(DDD 术语中的Bounded Context)。客户端和 API 本质上是一个使用 HTTP 作为粘合剂的应用程序。基本上我们没有使用 API 来与客户端解耦。这在构建类似Backend For Frontend 的东西时经常发生。在分层方面,您可能有以下几点:
现在,在这种情况下构建 API 时,您可能想走捷径,只需将前端请求传递到下游,并进行最少的翻译。基本上保持 API 层薄。该模型无论如何都会强制执行业务规则(例如通过抛出异常)。 Web 框架会自动将这些类型的崩溃转换为 500 错误。最终结果是您可能不会向客户端返回“正确”的 400 或 412 状态代码,而是返回 500,这从 HTTP 的角度来看是不正确的。但猜猜怎么了。您的客户不在乎,也不区分 400 和 500 - 它对它们一视同仁。两者都被认为是“应用程序”中的错误。客户端可能对503有特殊处理,但我认为这是一个单独的考虑。
// internal api with a tightly coupled consumer (think SPA)
public Response BuyLifeInsurance(Request request) {
// how much upstream and downstream logic do you want to
// duplicate here knowing that _downstream still enforces
// all the rules and will throw on violation? And client
// also knows about the rules and the violation means a bug.
// Why think about 400 vs 412 semantics? Who cares if it
// is 400 or 500 if both are treated as bugs that should be
// fixed by _your_ team (either on client or server)? You
// should care about life insurance and not http purity anyway :)
// why not let the call below crash and return 500 and figure
// out where the bug is based on logs?
_downstream.BuyLifeInurance(
new Age(request.age),
new Amount(request.amount));
}
基本上在某些情况下,复制已经在客户端实现并在下游模型中强制执行的业务逻辑可能是一种浪费。因此,API 层也可以以牺牲理论纯度为代价保持薄。我将这种情况称为弱 API™。一个有用的实验——如果它不是 HTTP,你会在多大程度上关注与协议习惯用法的兼容?如果这不是一个 API 而是一个简单的内部库怎么办?恕我直言,只有在您使用 API 与客户端分离时才需要付费(这是大多数情况,但也有我上面概述的例外情况)。
【讨论】:
我发现通过添加 error: error.detail 了解错误详细信息将返回一个响应,帮助指导我发送什么错误代码。在下面的代码中,响应(使用 Postman)指出了客户端或用户的错误请求。这使得错误代码 400 合适。
请注意,输入任何状态代码(例如 500)都不会产生影响,因为这只是一个假设。但是,错误详细信息将为您指明正确的方向。
如果(错误){
return res.status(400).send({
type: error.name,
status: "Failure",
error: error.detail,
message: "Please check the error message and try again!"
});
}
【讨论】: