资源已经存在时POST的HTTP响应代码


842

我正在构建一个允许客户端存储对象的服务器。这些对象在客户端完全构建,并带有在对象整个生命周期中永久存在的对象ID。

我已经定义了API,以便客户端可以使用PUT创建或修改对象:

PUT /objects/{id} HTTP/1.1
...

{json representation of the object}

{id}是对象ID,因此它是Request-URI的一部分。

现在,我也在考虑允许客户端使用POST创建对象:

POST /objects/ HTTP/1.1
...

{json representation of the object, including ID}

由于POST的意思是“追加”操作,因此我不确定在对象已经存在的情况下该怎么做。我应该将请求视为修改请求还是应该返回一些错误代码(哪个)?


5
截至6月2016 FB公然在登记台200时的电子邮件存在
格林

4
当尝试使用已经使用的名称创建资源(团队/仓库)时,Github API返回422
Ken Ken

1
这取决于您是否认为对象的存在错误。如果您处理追加,则200或204是最合适的响应代码。
Suncat2000

Answers:


1055

我的感觉是409 Conflict最合适的,但是,在野外自然很少见到:

由于与资源的当前状态存在冲突,因此无法完成请求。仅在预期用户可能能够解决冲突并重新提交请求的情况下才允许使用此代码。响应主体应该包含足够的信息,以使用户能够识别冲突的根源。理想情况下,响应实体应包括足够的信息供用户或用户代理解决问题。但是,这可能是不可能的,也不是必需的。

响应PUT请求最有可能发生冲突。例如,如果正在使用版本控制,并且正在PUT的实体包括对资源的更改,该更改与先前的(第三方)请求所进行的更改冲突,则服务器可能会使用409响应来指示它无法完成请求。在这种情况下,响应实体可能会以响应Content-Type定义的格式包含两个版本之间差异的列表。


11
为什么不去400 Bad Request?对我来说,这有点像验证错误(您提供了带有非法ID的错误有效负载)。
manuel aldana 2010年

314
400 => “由于语法格式不正确,服务器无法理解该请求”。并且服务器理解得很好,但是由于冲突而无法遵从。请求和语法没有错,只有数据问题。400会使我立即相信我正在使用的整个机制存在缺陷,而不仅仅是数据。
Wrikken 2010年

42
@Wrikken不再正确。HTTP 400在RFC 7231中进行了更改,以表示“ 由于某些东西被认为是客户端错误(例如,格式错误的请求语法,无效的请求消息框架或欺骗性的请求路由),服务器无法或不会处理请求”。我不是说400就是在这种情况下正确使用,但它可能是正确的与400的新定义
javajavajavajavajava

19
@javajavajavajavajava:重复的数据在我看来仍然不是一个“客户端错误”,但这当然是情人眼中的。
Wrikken

21
我返回HTTP 409Location标头指向现有/冲突的资源。
吉利

100

根据RFC 7231如果处理POST的结果等效于现有资源的表示形式,则可以使用303 See Other


4
我认为,这很可能是公认的答案。尽管“ MAY”表示完全可选的项目,但这是RFC 7231官方文档建议的唯一响应代码。
南道

16
这是最RESTful的答案。
赛斯

6
我认为背景很重要。例如:返回303表示需要重定向到找到的资源。在服务器到服务器的调用中这可能很有意义,但是如果您正在执行用户注册过程,那将毫无意义。
Sinaesthetic,2015年

11
抱歉,我对此表示反对。HTTP 300是关于重定向的,重定向到另一个可能具有不同属性的对象将极具误导性。
Michael Scheper

6
您不必后悔。但是,如果表示形式等效于现有资源,那么它如何具有不同的属性?而且即使有重定向,也会如何引起误导?OP说:如果对象已经存在,我不确定该怎么办。实际上,它是“相同”的对象。为什么重定向会产生误导?您所谈论的是OP中显然不是的另一个对象
Nullius

86

我个人使用WebDAV扩展422 Unprocessable Entity

根据RFC 4918

422 Unprocessable Entity状态代码装置的服务器理解的内容类型的请求实体(因此一个的415 Unsupported Media Type状态码是不适当的),并且请求实体的语法是正确的(因此一个400 Bad Request状态码是不适当的),但无法处理所包含的指令。


19
这是一个有趣的想法,促使我最终阅读了WebDAV RFC。但是,我认为422的含义是请求和包含的实体在语法上是正确的,但在语义上是没有意义的。
vmj

4
格式错误的JSON在语法上不是正确的实体,因此让422我感到奇怪……
awendt 2014年

7
我不会这样做。从答案中引用的同一URL:“例如,如果XML请求主体包含格式正确(即,语法正确)但语义上错误的XML指令,则可能会发生此错误情况。” 这是不可处理实体的真实含义,与发送带有有效语法和语义的完全有效的请求实体不同,唯一的问题是它与现有实体冲突。实际上,如果请求实体的语义无效,则根本不应该存在类似的现有实体。
塔默·斯拉什

1
添加到Tamer注释中,如果第二个请求首先出现,那么它将成功,如果从语义上讲是正确的,则不可能。因此,正确的语义将不适用于此处。
哈里斯(Harish)2016年

4
@Tamer为什么会这样?命令“请创建对象xy”在语法上正确。仅在可能创建对象xy时,这在语义上是正确的。如果对象xy已经存在,则无法再创建它,因此这是一个语义错误。
哈根·冯·埃岑

47

这全都与上下文有关,还有谁负责处理请求中的重复项(服务器或客户端或两者)


如果服务器仅指向重复项,请查看4xx:

  • 400错误的请求-服务器由于明显的客户端故障而无法处理请求时
  • 409冲突-如果服务器不处理请求,但是原因不是客户端的错误
  • ...

隐式处理重复项,请查看2XX:

  • 200 OK
  • 创建了201
  • ...

如果服务器期望返回某些内容,请查看3XX:

  • 找到302个
  • 303查看其他
  • ...

当服务器能够指向现有资源时,则意味着重定向。


如果以上还不够,在响应正文中准备一些错误消息始终是一个好习惯。


2
该请求不是复制资源,而是将数据附加到一个。我认为,您的答案是最好的答案。
Suncat2000

28

也许游戏晚了,但是我在尝试制作REST API时偶然发现了这个语义问题。

为了扩大Wrikken的回答一点,我想你可以使用两种409 Conflict403 Forbidden视情况而定-简而言之,使用403错误时,用户可以什么都不做,解决冲突和完成请求(例如,它们不能发送DELETE请求以明确删除资源),或者在可能的情况下使用409。

10.4.4 403禁止

服务器理解了该请求,但拒绝执行该请求。授权将无济于事,并且不应重复该请求。如果请求方法不是HEAD,并且服务器希望公开为什么未满足请求,则应在实体中描述拒绝原因。如果服务器不希望将此信息提供给客户端,则可以改用状态代码404(未找到)。

如今,有人说“ 403”,然后想到权限或身份验证问题,但是规范说这基本上是服务器告诉客户端它不打算这样做,不要再询问,这就是客户端为什么不这样做的原因。没错

至于PUTvs. POST... POST,当用户无法或不应为资源创建标识符时,应使用它来创建资源的新实例。PUT当资源的身份已知时使用。

9.6放置

...

POST和PUT请求之间的根本区别体现在Request-URI的不同含义上。POST请求中的URI标识将处理封闭实体的资源。该资源可能是一个数据接受过程,某个其他协议的网关或一个接受注释的单独实体。相比之下,PUT请求中的URI标识请求中包含的实体-用户代理知道URI的意图,并且服务器不得尝试将请求应用于其他资源。如果服务器希望将请求应用于其他URI,

它必须发送301(永久移动)响应;然后,用户代理可以自行决定是否重定向请求。


7
我认为403禁止意味着,即使用户的认证,他是不是授权给执行请求的操作。我不会将其用于验证错误。示例:未登录,我尝试删除某些内容。服务器向我发送了401未经授权(我的名字很不好,应该是401未经授权)。我登录并重试。这次服务器检查我的权限,看到我被禁止并返回403 Forbidden。也看到这个问题
Stijn de Witt

嗯...是的。这里的想法是正确的告诉用户,他们的授权使资源在OP的用例中不可变-它已经存在,您无权做任何事情来解决冲突,不要尝试再次创建资源。
p0lar_bear

3
根据规范,暗示错误409无法由POST请求返回(正确使用时),因为它指出应在与目标资源冲突时返回错误409 。由于目标资源尚未发布到目标资源,因此它不可能发生冲突,因此进行答复409 Conflict毫无意义。
Grant Gryczan

1
我不会推断409错误不能由返回POST,实际上,我会推断相反的原因,因为“ 最有可能在响应PUT请求时发生冲突”。似乎表明其他请求方法也可以使用此代码。此外,“响应主体包含足够的信息供用户识别冲突的根源。理想情况下,响应实体应包含足够的信息供用户或用户代理解决问题;但是,这可能是不可能的,并且不需要。” (webdav.org/specs/rfc2616.html#status.409
JWAspin

14

“ 302 Found”对我来说听起来很合逻辑。和RFC 2616说,它比GET和HEAD其他请求回答(这当然包括POST)

但是,它仍然使访问者可以通过RFC访问该URL以获得此“找到的”资源。为了使其直接进入真实的“找到的” URL,应该使用“ 303 See Other”,这是有道理的,但是会强制另一个调用以获取其后续URL。从好的方面来说,此GET是可缓存的。

我认为我会使用“ 303 See Other”。我不知道我是否可以使用正文中找到的“内容”进行响应,但是我想这样做是为了节省一次往返服务器的时间。

更新:重新阅读RFC之后,我仍然认为不存在的 “ 4XX + 303 Found”代码应该是正确的。但是,“ 409冲突”是最好的现有答案代码(由@Wrikken指出),可能包括指向现有资源的Location标头。


88
3xx状态用于重定向
Aviram Netanel 2014年

1
“请求的资源临时位于其他URI下。” 从w3.org/Protocols/rfc2616/rfc2616-sec10.html
Statueofmike 2014年

1
恕我直言,“ 307临时重定向”是真正的临时重定向。“ 302”含糊不清,但“找到了!” 是这里真正想要的信息。最好的明确折衷方案是HTTP语义上的“ 303 See Other”。我会选择“ 303 See Other”。
alanjds 2014年

@DavidVartanian哼...我在这里没有看到错误。客户发送了正确的请求,但是怎么说“对不起,但是您要在此处创建的内容已经存在”?似乎需要3xx的工作。对我来说,这不是4xx,因为没有客户端错误。
alanjds 2015年

1
@DavidVartanian感谢您的讨论。将答案更新为409。委托人错误地要求不可能的东西,即使它不知道这是不可能的。
alanjds

11

我不认为你应该这样做。

如您所知,POST是用来修改集合的,它用于创建新项目。因此,如果您发送ID(我认为这不是一个好主意),则应该修改集合,即修改项目,但这会造成混淆。

使用它添加没有ID的项目。这是最佳做法。

如果要捕获UNIQUE约束(而不是id),则可以响应409,就像在PUT请求中一样。但不是ID。


具有连接表关系的对象呢?假设我们有account,product和account_product作为数据库表。我想将产品添加到帐户中,所以我想使用product_id将其发布到/ account / {id} / product。如果只允许一种帐户与产品的关系,我应该退还什么?
partkyle 2014年

2
忘记数据库表。假设一个产品只能与一个帐户相关联...那么它是一对多的关系。因此,使用{'account':account_id}进行POST / product / {id}。如果将最大基数设置为“ 1”(一对一关系)....为什么它们将其余对象分开?基数错误将仅为400 err。把事情简单化。希望我能理解您的问题。
阿方索·蒂恩达

我也提出了这个问题,对我来说,ID不是数据库上的技术ID,而是公司代码之类的东西。在此应用程序中,经理用户可以创建公司并必须为其提供代码。尽管数据库表也具有技术ID,但这仍是用户的公司ID。因此,就我而言,如果已经存在相同的公司代码,我将返回409。
AlexCode 2014年

@partkyle停止将PK用作公共ID!
Sinaesthetic

一些实体对它们有唯一的约束,而不仅仅是id。就像帐户一样,如果用户不提供用户名,则无法创建帐户。而且添加没有用户名的帐户显然是不可能的
rocketspacer

9

我会去 422 Unprocessable Entity,当请求无效但问题不在语法或身份验证中时,将使用。

作为反对其他答案的一个论据,使用任何非4xx错误代码将意味着它不是客户端错误,而显然是。要使用非4xx错误代码表示客户端错误根本没有任何意义。

看起来 409 Conflict是最常见的答案,但是根据规范,这意味着资源已经存在,并且您要为其应用的新数据与其当前状态不兼容。如果您要发送POST请求(例如,已使用的用户名)实际上与目标资源没有冲突,因为尚未发布目标资源(您要创建的资源)。当存储的资源版本与请求的资源版本之间存在冲突时,这是专门针对版本控制的错误。这对于此目的非常有用,例如,当客户端缓存了旧版本的资源并基于该错误的版本发送请求时,该错误的版本将不再有条件地有效。“在这种情况下,响应表示可能包含基于修订历史记录合并差异的有用信息。” 使用该用户名创建另一个用户的请求是无法处理的,与版本控制无关。

记录一下,422也是GitHub尝试通过已经使用的名称创建存储库时使用的状态代码。


422是webdav规范,因此我不建议您将其用于REST API
rwenz3l,

7

我认为对于REST,您只需要确定特定系统的行为即可,在这种情况下,我认为“正确”的答案将是此处给出的几个答案之一。如果您希望请求停止并表现为客户端在继续操作之前犯了一个需要解决的错误,则使用409。如果冲突确实不是那么重要,并且希望保持请求继续进行,则通过重定向请求来进行响应。找到的实体的客户。我认为适当的REST API无论如何应在POST之后重定向(或至少提供位置标头)到该资源的GET端点,因此这种行为将提供一致的体验。

编辑:值得注意的是,由于要提供ID,因此应考虑使用PUT。然后,行为就很简单:“我不在乎现在的内容,把它放在那里。” 意思是,如果什么都不存在,它将被创建;如果有东西将被替换。我认为服务器管理该ID时更适合使用POST。分离这两个概念基本上会告诉您如何处理(即PUT是幂等的,因此只要有效负载验证,它就始终可以工作,POST总是创建,因此,如果ID发生冲突,则409将描述该冲突)。 。


根据规范,暗示错误409无法由POST请求返回(正确使用时),因为它指出应在与目标资源冲突时返回错误409 。由于目标资源尚未发布到目标资源,因此它不可能发生冲突,因此进行答复409 Conflict毫无意义。
Grant Gryczan18年

有争议的imo。如果您发布到/ users,则资源是集合,而不是单个记录/ users / {id}
Sinaesthetic,2018年

当存储的资源版本与请求的资源版本之间存在冲突时,这是专门针对版本控制的错误。这对于此目的非常有用,例如,当客户端缓存了旧版本的资源并基于该不正确的版本发送请求时,该条件不再有效。“在这种情况下,响应表示可能包含基于修订历史记录合并差异的有用信息。”
Grant Gryczan

我确实喜欢您的建议PUT
Grant Gryczan

4

毕竟,另一种可能的治疗方法是使用PATCH。PATCH定义为可更改内部状态且不限于附加的内容。

PATCH通过允许您更新已经存在的项目来解决该问题。请参阅:RFC 5789:补丁


2
修补程序就像PUT,但不是完整的替代品。它用于修改资源的一部分,例如添加,删除或修改资源的单个元素,而不是整体替换它。
Sinaesthetic '16

4

为什么不接受202?这是一个正常的请求(200s),本身没有客户端错误(400s)。

来自10个状态码定义

“ 202已接受。请求已被接受进行处理,但是处理尚未完成。”

...因为它已经存在,所以不需要完成。客户不知道它已经存在,他们没有做错任何事情。

我倾向于抛出202,并返回与GET返回的内容相似的内容/{resource}/{id}


21
这个答案是错误的。202表示服务器未发现请求问题,而是选择在响应后处理请求。这也意味着它期望处理成功。在我们的例子中,服务器知道处理将失败,因此202是错误的响应。
阿德里安

4
202的示例是队列或订阅。换句话说,如果您现在要查询请求的结果,则可能无法立即获得该结果。
Sinaesthetic

1
如果服务器仍在处理请求,这将是适当的。200或204会更常见。由于OP正在发出追加请求,因此对象的存在是预期的条件,而不是错误。
Suncat2000

向客户说请求被接受是没有意义的,因为您已经知道请求没有被接受!
lucastamoios

1
@Adrian和lucastamoios我认为你们都假设服务器在提供响应之前从数据库同步读取。并非总是这样,因此此答案并非“错误”,因为服务器并不总是“知道”现有记录。在异步系统中,这是非常常见的情况,在异步系统中,api层仅记录后台工作人员处理的请求。
gsaslis

2

在检查重复记录的正确代码时偶然发现了这个问题。

请原谅我的无知,但我不明白为什么每个人都忽略了代码“ 300”,该代码清楚地表明了“多项选择”或“模棱两可”

在我看来,这将是构建非标准或特定系统供您自己使用的完美代码。我也可能是错的!

https://tools.ietf.org/html/rfc7231#section-6.4.1


我的理解是:“状态代码表示目标资源具有多个表示形式...正在提供有关替代项的信息,以便用户(或用户代理)可以通过将其请求重定向到一个或多个表示形式来选择首选表示形式标识符”,我们明确地试图防止出现多种表示形式。没有选择。客户没有其他选择。客户应使用其他ID重新提交。话虽如此,还应该考虑在客户端还是服务器中是否应该生成唯一的ID。
musicin3d

语义上,客户端说“创建此”,而服务器通过说“转到此处”进行响应。谈话没有任何意义。几乎就像服务器在告诉客户端“改为发布到此位置”一样。如果服务器响应“好,我创建了它并且就在这里”,则300s更适合于GET请求或POST
。.– Sinaesthetic,

2

更有可能 400 Bad Request

6.5.1。400错误的要求


400(错误请求)状态代码表示服务器由于某些原因(例如格式错误的请求语法,无效的请求消息框架或欺骗性的请求路由)而被视为客户端错误,因此服务器无法处理该请求。

由于请求包含重复值(已经存在的值),因此可以将其视为客户端错误。下一次尝试之前,需要更改请求。
考虑到这些事实,我们可以得出HTTP STATUS 400错误请求的结论。


1
错误请求意味着数据包的语法存在固有的问题。如果在另一种情况下(如资源不已经存在),数据包会成功,那么它不应该返回错误400
格兰特Gryczan

1

208- http://httpstatusdogs.com/208-already-reported怎么样?那是一个选择吗?

我认为,如果唯一的事情是重复资源,则不会出现任何错误。毕竟,客户端或服务器端都没有错误。


由于您要附加ID已存在的特定项目,因此这不是选项。因此,您尝试添加一些东西,但这已经存在。仅当数据集增长时,“确定”才适用。追加内容->好,我什么都没追加。我猜不合适。
Martin Kersten 2015年

正如我所说,我认为这不是错误。但是我明白@martin的观点
Fernando Ferreira

如果没有成功创建资源,则根据定义存在错误。
Grant Gryczan

POST也用于附加数据。这是由定义不是一个错误
Suncat2000

@ Suncat2000即使是这种情况,如果没有成功添加数据,仍然会出现错误。如果资源已经存在,则不会添加任何数据。
格兰·格里赞
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.