400与422响应POST数据的区别

535

我正在努力确定在我正在开发的“类REST” API中不同场景下应该返回哪个正确的状态码。假设我有一个允许以JSON格式POST购买的终端点。它看起来像这样:

{
    "account_number": 45645511,
    "upc": "00490000486",
    "price": 1.00,
    "tax": 0.08
}
如果客户端发送给我“sales_tax”(而不是预期的“tax”),我应该返回什么?目前,我正在返回400。但是,我开始对此产生疑问。我是否真的应该返回422?我的意思是,这是JSON(被支持),它是有效的JSON,只是没有包含所有必需的字段。

1
可能是REST:将应用程序错误映射到HTTP状态码的重复问题。 - Anirudh Ramanathan
9个回答

579

在您提问时(以及我的原始答案),RFC 7231还不存在;那时我反对使用400 Bad Request,因为RFC 2616说(强调是我的):

由于语法错误,服务器无法理解请求。

而您描述的请求是语法上有效的JSON封装在语法上有效的HTTP中,因此服务器没有关于请求的语法问题。

现在看来,400 Bad Request似乎是您的用例中最好的HTTP/1.1状态码。

然而正如Lee Saferite在评论中指出的那样, RFC 7231,取代了RFC 2616,没有包括该限制:

400(错误请求)状态代码表示服务器由于认为是客户端错误(例如,格式错误的请求语法,无效的请求消息框架或欺骗性请求路由)而无法或不会处理请求。


然而,在重新表述之前(或者如果你想挑剔的话,RFC 7231现在只是一个提议标准),对于你的使用情况,422 Unprocessable Entity似乎不是一个错误的HTTP状态码,因为正如RFC 4918的介绍所说:

虽然HTTP/1.1提供的状态码足以描述WebDAV方法遇到的大多数错误条件,但有些错误并不很容易归入现有的类别。本规范定义了为WebDAV方法开发的额外状态码(第11节)

关于422的描述则说:

422(无法处理的实体)状态码表示服务器理解请求实体的内容类型(因此不适用415(不支持的媒体类型)状态码),并且请求实体的语法正确(因此不适用400(错误请求)状态码),但无法处理所包含的指令。(请注意语法的参考;我怀疑7231在某种程度上也使4918过时了)
这听起来与您的情况完全相同,但为防万一,它继续说:
例如,如果XML请求正文包含格式良好(即语法正确),但语义上错误的XML指令,则可能会出现此错误条件。
(将“XML”替换为“JSON”,我认为我们可以认为这是您的情况)
现在,有些人会反对RFC 4918是关于“Web分布式创作和版本控制的HTTP扩展(WebDAV)”,而您(可能)没有涉及WebDAV,因此不应使用其中的东西。

若在原标准中使用明确无法涵盖该情况的错误代码和从扩展中获取并描述该情况的错误代码之间进行选择,我会选择后者。

此外,RFC 4918第21.4节提到了IANA超文本传输协议(HTTP)状态码注册表,其中可以找到422。

我建议HTTP客户端或服务器完全可以在正确使用的前提下使用该注册表中的任何状态码。


但是从HTTP/1.1开始,RFC 7231已经得到了广泛应用,所以只需使用400 Bad Request即可!

12
你的回答(422)对我来说很有意义。这也是Rails(respond_with)在资源由于验证错误无法处理时使用的方法。 - Tyler Rick
16
请注意此处非 WebDAV 规范中使用了 422 错误码:http://tools.ietf.org/html/rfc5789#section-2.2 - Andrey Shchekin
5
更新一下,RFC 7231对响应码400的描述与以前不同,改变了语义。 - Lee Saferite
7
抱歉,我修改了这个答案以反映RFC的更改,失去了一些清晰度;我会尝试重新整理。使用422几乎肯定是安全的,但现在你应该使用400。 - Kristian Glass
6
我仍然认为规范可以更加清晰明了。文中提供的例子都是客户端做错的明显情况,OP的情况也属于此类。 然而,像“我知道你要求什么,但由于某些业务规则,我拒绝这样做”这种情况并不是那么明确。 这并不完全是客户端的错,因此根据同一规范,可能适用403状态码:“然而,由于与凭据无关的原因,请求可能被禁止”。 我更喜欢针对权限相关的内容和“不能完成”分别使用不同的状态码。 - Thorarin
显示剩余18条评论

186

案例研究: GitHub API

https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors

也许从知名API中复制是一个明智的想法:

There are three possible types of client errors on API calls that receive request bodies:

Sending invalid JSON will result in a 400 Bad Request response:

HTTP/1.1 400 Bad Request
Content-Length: 35
{"message":"Problems parsing JSON"}

Sending the wrong type of JSON values will result in a 400 Bad Request response:

HTTP/1.1 400 Bad Request
Content-Length: 40

{"message":"Body should be a JSON object"}

Sending invalid fields will result in a 422 Unprocessable Entity response:

HTTP/1.1 422 Unprocessable Entity
Content-Length: 149

{
  "message": "Validation Failed",
  "errors": [
    {
      "resource": "Issue",
      "field": "title",
      "code": "missing_field"
    }
  ]
}

12
无法更多地点赞。希望更多的点赞答案能够参考这个答案。 规范(RFC,IANA)在明确定义和区分两者方面失败了。因此,答案归结为最佳实践,而GitHub给了我们一个。 - Alex Klaus
6
这必须是被接受的答案。此外,回答中提供的URL已不再有效,会带您前往首页。最新的URL是:https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors。 - IllegalSkillsException
5
我认为这个答案不够清晰。缺失字段可能是未发送的字段,对我来说这显然是400,因为应用程序无法理解此有效负载,它违反了合同。在我看来,400更适合于格式不正确的JSON、具有不同键名(合同违规)的JSON以及一个或多个字段内容来自另一种类型的JSON,比如你期望一个整数,但得到了一个对象。即使是非空约束也可以在这两个状态码中都存在,如果根本没有发送字段,则为400(大多数框架将其视为null),如果发送了但值为null,则为422。 - Philippe Gioseffi
2
如果JSON格式不正确,即存在无效的必填字段,则返回400错误。如果JSON缺少可能需要处理请求的可选字段,则返回422错误。如果JSON包含有效字段,但字段的值阻止请求被处理(例如,资金不足),则返回422错误。对我来说,400和422之间的关键区别在于422表示服务器理解了请求,但由于值本身(而不是值的类型)无法完成请求。400表示服务器不会尝试处理请求,因为JSON不正确-存在无效字段/值类型。 - undefined
1
根据https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422:
请求实体的语法是正确的,但无法处理其中包含的指令。
这意味着有效载荷格式正确,服务器理解了请求。但由于其他原因,无法继续处理。 所以我同意@leo_cape。 这个答案是不正确的。
- undefined
显示剩余2条评论

46

400 Bad Request是你的使用场景下合适的HTTP状态码,该代码由HTTP / 0.9-1.1 RFC定义。

由于格式不正确,服务器无法理解请求。客户端应该在未经修改的情况下不重复请求。

https://www.rfc-editor.org/rfc/rfc2616#section-10.4.1

422 Unprocessable Entity由RFC 4918 - WebDav定义。请注意,与400相比存在轻微差异,请参见下面引用的文本。

如果XML请求体包含格式良好(即语法正确),但语义上有错误的XML指令,则可能会发生此错误条件。

为保持统一的接口,您应该仅在XML响应的情况下使用422,并且还应该支持Webdav扩展定义的所有状态代码,而不仅仅是422。

https://www.rfc-editor.org/rfc/rfc4918#page-78

另请参阅Mark Nottingham关于状态代码的帖子:

试图将应用程序的每个部分“深入”到HTTP状态代码中是错误的;在大多数情况下,您要瞄准的粒度级别要粗略得多。当有更好的选择时,请使用通用状态代码200 OK、400 Bad Request和500 Internal Service Error。

如何思考HTTP状态码


4
422状态码是IANA注册表http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml的一部分,因此任何IMHO都没有意义。无论如何,Facebook和Twitter REST API重新发明了自己的代码,并且不使用RFC/IANA标准。所以你可以这样做。 - gavenkoa
19
第11节明确表示,它们被添加到整个规范中,而不仅仅是在WebDav规范内部:“以下状态码被添加到HTTP / 1.1 [RFC2616]中定义的状态码列表中。” - Steve Tauber
11
仅仅因为代码被描述为WebDAV规范的一部分,并不意味着它是专门针对WebDAV的!状态码应该是通用的。 - treat your mods well
1
撇开对422确切含义的争议,我认为Mark Nottingham的引用是非常好的建议。还有这段话来自相关文章:“试图让你的应用程序“适应”一组状态代码只会带来痛苦和失望。不要这样做。” - Simon Elms

42

反映截至2015年的状态:

在行为上,客户端和中间件会将400和422响应代码视为相同,因此实际上使用哪个并没有具体区别。

然而,我希望看到目前更广泛地使用400,并且HTTPbis规范提供的澄清使其成为两个状态代码中更合适的一个:

  • HTTPbis规范澄清了400的意图不仅仅是用于语法错误。现在使用更广泛的短语“表示服务器由于某些被认为是客户端错误的原因无法或不会处理请求”。
  • 422是WebDAV扩展,未在RFC 2616或新的HTTPbis规范中引用。

为了更好的理解,HTTPbis是HTTP/1.1规范的修订版,旨在澄清不清晰或不一致的领域。一旦它获得批准状态,它将取代RFC2616。


4
那么403禁止访问状态码也可以用于这种情况吗?引用:“403(禁止)状态码表示服务器理解了请求但拒绝授权...如果请求中提供了身份验证凭据,服务器认为它们不足以授予访问权限....然而,由于其他原因,请求可能被禁止访问。”所以看起来403可以用于拒绝与身份验证无关的请求。 - garbagecollector
2
@garbagecollector 注意,“因凭据以外的原因被拒绝”不等于“因身份验证以外的原因被拒绝”。有很多方法可以在不使用凭据的情况下对某人进行身份验证。 - Knetic
@garbagecollector 不,凭证表示身份验证(即“你是谁”),如果失败将返回401。而授权(即“你能做什么”)失败会返回403。关于这方面的完整解释可以参考这里:https://dev59.com/oHA75IYBdhLWcg3wbojM#6937030 但这两种情况都不适用于 OP 的“缺少字段”问题,因为无论哪个用户尝试都会出现相同的错误。我认同400是正确的答案。 - Will Sheppard

23

由于你的请求中“语法”一词的定义不同,因此没有正确答案。最重要的是:

  1. 始终使用相同的响应代码
  2. 在响应正文中包含尽可能多的其他信息,以帮助使用您的API的开发人员弄清楚发生了什么。

在大家抨击我说这里没有对错答案之前,请允许我解释一下我的结论是如何得出的。

在这个特定的例子中,OP的问题是关于一个包含与预期不同的键的JSON请求。现在,根据自然语言的角度来看,接收到的键名非常相似,但它是严格不同的,因此(通常)不能被机器识别为等价的。

正如我上面所说,决定因素是“语法”的含义。如果请求以Content Type为“application/json”发送,则是的,该请求在语法上是有效的,因为它是有效的JSON语法,但在语义上无效,因为它与预期的不匹配(假设严格定义了有关该请求是否语义上有效的标准)。

另一方面,如果请求以更具体的自定义Content Type的形式发送,例如“application/vnd.mycorp.mydatatype+json”,则该请求很可能在语法上无效,因此会返回400响应(也许说明了预期需要哪些字段)。

在这种情况下,由于“键”不正确,而不是“值”,如果有关于有效键的规范,则存在“语法”错误。如果没有关于有效键的规范,或者错误出现在值上,则是“语义”错误。


非常低估的答案 - 感谢您措辞清晰的解释。 - puiu
2
完全同意!我来自XML SOAP背景,概念模式已经深入我的血液,而JSON文档则不会公布其模式。对我来说,重要的是服务器是否“理解”请求。如果服务器不知道“sales_tax”是什么,那么它只会简单地返回400:“我不知道你发送给我的是什么,但绝对不是我想要的。”。 - Aleksander Stelmaczonek
谢谢,现在我明白应该使用哪一个了。 - Wesley Brian Lachenal

4

422未处理实体的解释 更新日期:2017年3月6日
什么是422未处理实体?
当请求格式正确但由于语义错误无法被处理时,会出现422状态码。这个HTTP状态码是在RFC 4918中引入的,并且更具体地针对Web分布式作者和版本控制(WebDAV)的HTTP扩展。
有些争议认为开发人员是否应该向客户端返回400或422错误(下面更多关于两种状态之间的区别)。然而,在大多数情况下,人们一致认为只有在支持WebDAV功能时才应返回422状态。
从RFC 4918第11.2节中摘取的422状态码的逐字定义如下。
422(未处理实体)状态码表示服务器理解请求实体的内容类型(因此,415(不支持的媒体类型)状态码不合适),并且请求实体的语法是正确的(因此,400(错误请求)状态码不合适),但无法处理其中包含的说明。
定义继续说:
例如,如果XML请求正文包含形式良好(即句法正确),但语义上错误的XML指令,则可能会发生此错误条件。
400与422状态码
错误请求使用400状态码,并且如果请求语法格式错误,包含无效的请求消息框架或具有欺骗性的请求路由,则应将其返回给客户端。这个状态码似乎与422未处理实体状态非常相似,但区分它们的一个小细节是,对于生成400错误的请求的语法是不正确的,而对于422错误的请求实体的语法是正确的。
422状态的使用应该仅保留给非常特定的用例。在大多数其他情况下,由于语法格式不正确导致客户端错误发生时,应使用400错误请求状态。

https://www.keycdn.com/support/422-unprocessable-entity/


3

您的情况:从REST的角度来看,HTTP 400是您的情况下正确的状态代码,因为发送sales_tax而不是tax在语法上是不正确的,尽管它是一个有效的JSON。当将JSON映射到对象时,大多数服务器端框架通常会强制执行此操作。但是,有一些REST实现会忽略JSON对象中的新key。在这种情况下,可以通过服务器端强制执行自定义content-type规范以仅接受有效字段。

422的理想情况:

在理想的情况下,如果服务器了解请求实体的内容类型并且请求实体的语法正确但无法处理数据,因为其在语义上是错误的,则发送422作为响应通常是首选并且可接受的。

400优于422的情况:

请记住,响应代码422是扩展的HTTP(WebDAV)状态代码。仍然有一些HTTP客户端/前端库没有准备好处理422。对于他们来说,这很简单,就像“HTTP 422是错误的,因为它不是HTTP”一样。从服务的角度来看,400并不是非常具体的。

在企业架构中,服务大多部署在服务层(如SOA、IDM等)上。它们通常为多个客户端提供服务,从非常旧的本地客户端到最新的HTTP客户端。如果其中一个客户端无法处理HTTP 422,则选项是要求客户端升级或将响应代码更改为HTTP 400,适用于所有人。根据我的经验,这在今天很少见,但仍然有可能发生。因此,在决定HTTP响应代码之前,始终需要仔细研究您的架构。

为了处理这种情况,服务层通常使用版本控制或设置配置标志以严格遵循HTTP规范的客户端发送400,并为其余客户端发送422。这样,他们为现有消费者提供向后兼容性支持,同时为新客户端提供使用HTTP 422的能力。


RFC7321的最新更新如下:

The 400 (Bad Request) status code indicates that the server cannot or
   will not process the request due to something that is perceived to be
   a client error (e.g., malformed request syntax, invalid request
   message framing, or deceptive request routing).

这意味着服务器可以针对无效的请求发送HTTP 400状态码。然而,400现在不仅指语法错误,但如果客户端能够处理,则422仍然是一种有效的响应。


2

400 - 请求验证失败,例如数据缺失、类型错误等,因此被赋予了400状态。

422 - 请求验证通过,但操作过程失败,因为请求数据或其中一部分向操作发生错误,但已处理并赋予了422状态。


-3

首先,这是一个非常好的问题。

400 Bad Request - 当请求中缺少关键信息时

例如,授权头或内容类型头。服务器绝对需要这些信息才能理解请求。这可能因服务器而异。

422 Unprocessable Entity - 当请求正文无法解析时

这比400轻微。请求已经到达服务器。服务器已经确认请求的基本结构正确。但是请求正文中的信息无法解析或理解。

例如,当请求正文为JSON时,Content-Type: application/xml

这里有一篇文章列出了状态码及其在REST API中的使用。 https://metamug.com/article/status-codes-for-rest-api.php


10
422 表示语法是正确的,但内容不符合要求。 在期望 XML 的情况下发送 JSON,意味着语法错误,因此在这种情况下正确的响应应该是 400。 - Dirk
1
正如Dirk所说,422意味着语法上是有效的请求(可以被解析和理解),但在语义上无效。 - Jacek Obarymski
400:当请求由于无效语法(例如解析错误)而无法处理时;422:当请求由于无效数据(例如验证错误)而无法处理时。 - Kitanotori
1
你的422示例无效,因为通过发送带有应用程序/xml媒体类型的JSON,请求正文会自动出现语法错误,因此响应应该是400。 - manemarron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接