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

vmj*_*vmj 766 rest http

我正在构建一个允许客户端存储对象的服务器.这些对象在客户端完全构造,完整的对象ID对于对象的整个生命周期是永久的.

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

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

{json representation of the object}
Run Code Online (Sandbox Code Playgroud)

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

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

POST /objects/ HTTP/1.1
...

{json representation of the object, including ID}
Run Code Online (Sandbox Code Playgroud)

由于POST意味着"附加"操作,我不知道如果对象已经存在该怎么做.我应该将请求视为修改请求还是应该返回一些错误代码(哪个)?

Wri*_*ken 951

我的感觉是409 Conflict最合适的,然而,当然在野外很少看到:

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

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

  • 400 => _"由于语法格式错误,服务器无法理解请求"_.服务器完全理解,但由于冲突而无法遵守.请求和语法没有问题,只有数据问题.400会立即让我相信我使用的整个机制是有缺陷的,而不仅仅是数据. (290认同)
  • @Wrikken那不再正确.在[RFC 7231](https://tools.ietf.org/html/rfc7231#page-58)中更改了HTTP 400,意思是"服务器不能或**不会因为某些事情而处理请求被认为是客户端错误(例如,格式错误的请求语法,无效的请求消息框架或欺骗性请求路由)."_我不是说400在这种情况下是正确的用法,但它可以用400的新定义正确. (35认同)
  • 我返回`HTTP 409`,其中`Location`标题指向现有/冲突资源. (18认同)
  • @javajavajavajavajava:在我看来,重复的数据并不是一个"客户端错误",但这当然是旁观者的眼睛. (17认同)
  • 为什么不去400 Bad Request?对我来说,这看起来有点像验证错误(您使用非法ID提供错误的有效负载). (11认同)
  • 发出 409 而不是通用 400 还可以让恶意用户找到系统中已经存在的电子邮件。 (6认同)
  • @MavrickLaakso 如果您具有真正的安全性,那么在任何系统中发现资源存在的能力都不是问题。您所建议的是“通过默默无闻实现安全”,这通常被认为不是真正的安全。此外,如果用户尝试注册服务的电子邮件地址,仅返回“400”是没有用的,用户需要知道电子邮件已被占用,以便他们可以选择其他内容。一般来说,您始终可以在尝试创建新资源之前对用户进行身份验证。 (5认同)
  • 不是.两种定义中400的意图是告诉客户端因为请求不好而停止尝试. (3认同)
  • @dhj可能是因为Auth0人员阅读了此页面 (2认同)
  • 没有什么是“禁止的”。它们只是规格。您不必跟随他们。但是,如果您愿意,我认为422更合适。另外,303是重定向码。您没有重定向到任何东西。 (2认同)
  • 第422章 作为补充,我同意@GrantGryczan(/sf/answers/3479668321/)422不可处理的实体,当请求无效但问题不在于语法或身份验证时使用 (2认同)

Nul*_*ius 87

根据RFC 7231,可以使用303 See Other MAY 如果处理POST的结果等同于现有资源的表示.

  • 这是最RESTful的答案. (12认同)
  • 对不起,我正在低估这个.HTTP 300s是关于重定向的,并且重定向到可能具有不同属性的另一个对象将是非常误导的. (11认同)
  • 我认为背景很重要.例如:返回303意味着需要重定向到找到的资源.这在服务器到服务器调用中可能有意义,但如果您正在运行用户注册过程,那么根本没有任何意义. (6认同)
  • 你不必抱歉.但是,如果表示等同于现有资源,它如何具有不同的属性?即使它有,重定向将如何误导?OP说:_我不知道如果对象已经存在该怎么办.它实际上是'相同'的对象.为什么重定向会产生误导?你在谈论另一个对象_在OP的头脑中显然不是. (5认同)
  • 在我看来,这可能是一个公认的答案.虽然"MAY"表示完全可选项,但它是官方[RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.3)文档中建议的唯一响应代码. (4认同)
  • 只是FYI .. IE没有返回303的响应正文,如果这很重要. (2认同)
  • 从语义上讲,客户端说“创建这个”,服务器则通过说“改为转到这里”来响应。谈话没有任何意义。这几乎就像服务器告诉客户端“改为发布到此位置”。在服务器响应“好吧,我创建了它,它就在这里”的情况下,300 更适合对 GET 请求或 POST 进行响应。 (2认同)
  • @Sinaesthetic:我认为在某种情况下让客户知道'嘿,这个已经存在,你可以在这里找到它'是完全没错的.我提到的'我创建它并且它已经在这里',应该提供带有201 Created或200 Ok状态代码的Content-Location标头.因此,如果已创建某些内容,则只有200和201是有效的返回代码.这也在我在原始答案中链接的相同RFC中明确说明.我同意303在所有情况下或所有商业案例中可能都不是正确的答案.这也是他们在RFC中强调MAY的原因. (2认同)

Gar*_*eth 81

我个人使用WebDAV扩展422 Unprocessable Entity.

根据RFC 4918

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

  • 这是一个有趣的想法,促使我最终阅读了WebDAV RFC.但是,我认为422的含义是请求和包含的实体在语法上是正确的,但在语义上没有意义. (19认同)
  • 我不会这样做.从答案中引用的相同URL:"例如,如果XML请求主体包含格式正确(即语法正确)但语义错误的XML指令,则可能会出现此错误情况." 这是一个不可处理的实体的真正含义,与您使用有效语法和语义发送完全有效的请求实体的情况不同,但唯一的问题是它与现有实体冲突.**实际上,如果请求实体的语义无效,则根本不应存在类似的现有实体.** (6认同)
  • 格式错误的JSON不是一个语法正确的实体,所以`422`让我感到奇怪...... (4认同)
  • @Tamer为什么会这样?命令"Please create object xy"在语法上是正确的.仅当可以创建对象xy时,它在语义上是正确的.如果对象xy已经存在,则无法再创建它,因此这是一个语义错误. (4认同)

wom*_*ire 47

在阅读了这篇文章以及其他几个长达数年的关于状态代码使用的讨论之后,我得出的主要结论是必须仔细阅读规范,重点关注所使用的术语、它们的定义、关系和周围的上下文。

相反,从不同的答案中可以看出,经常发生的情况是,规范的某些部分被脱离了上下文,并根据感觉和假设进行了孤立的解释。

这将是一个相当长的答案,其简短摘要是HTTP 409 是报告“添加新资源”操作失败的最合适的状态代码,以防具有相同标识符的资源已存在。以下是仅基于权威来源RFC 7231中所述内容的解释。

那么为什么409 Conflict在OP问题中描述的情况下最合适的状态代码是?

RFC 7231对409 Conflict状态码的描述如下:

409(冲突)状态代码表示由于与目标资源的当前状态发生冲突,请求无法完成。

这里的关键组件是目标资源及其状态

目标资源

该资源由 RFC 7231 定义如下:

HTTP 请求的目标称为“资源”。HTTP 不限制资源的性质;它仅仅定义了一个可用于与资源交互的接口。每个资源都由统一资源标识符 (URI) 标识,如 [RFC7230] 的 2.7 节中所述。

因此,当使用 HTTP 接口时,我们总是通过对 URI 标识的资源应用 HTTP 方法来对其进行操作。

当我们打算添加新资源时,根据OP的示例,我们可以:

  • PUT与资源一起使用/objects/{id}
  • POST与资源一起使用/objects

/objects/{id}出于兴趣,因为使用方法时不能发生冲突PUT

PUT 方法请求创建目标资源的状态用请求消息有效负载中包含的表示定义的状态替换目标资源的状态。

如果具有相同标识符的资源已经存在,则会被替换为PUT

因此,我们将重点关注/objects资源和POST

RFC 7231 说道POST

POST 方法请求目标资源根据资源自身的特定语义处理请求中包含的表示。例如,POST 用于以下功能(除其他外): ... 3) 创建尚未被源服务器识别的新资源;4) 将数据附加到资源的现有表示。

与OP理解方法相反POST

由于 POST 意味着“追加”操作......

将数据附加到资源的现有表示只是可能的“功能”之一POST。此外,OP 在提供的示例中实际所做的并不是直接将数据附加到/objects表示,而是创建一个新的独立资源/objects/{id},然后该资源成为表示的一部分/objects。但这并不重要。

重要的是资源表示的概念,它让我们……

资源状态

RFC 7231 解释:

考虑到资源可以是任何东西,并且 HTTP 提供的统一接口类似于一个窗口,通过该窗口,人们只能通过与另一端的某个独立参与者通信消息来观察和操作此类事物,因此抽象是需要在我们的通信中表示(“取代”)该事物的当前或期望状态。该抽象称为表示 [REST]。

就 HTTP 而言,“表示”是旨在以可以通过协议轻松传达的格式反映给定资源的过去、当前或所需状态的信息,并且由一组表示组成元数据和潜在无限的表示数据流。

这还不是全部,规范继续描述表示部分 - 元数据和数据,但我们可以总结一下,由元数据(标头)和数据(有效负载)组成的资源表示反映了资源的状态

现在我们已经有了理解状态代码用法所需的两个部分409 Conflict

409 冲突

让我们重申一下:

409(冲突)状态码表示由于与目标资源的当前状态冲突而无法完成请求。

那么如何搭配呢?

  1. 我们POST= /objects> 我们的目标资源是/objects
  2. OP 没有描述/objects资源,但该示例看起来像是一个常见场景,其中/objects是一个资源集合,包含所有单独的“对象”资源。也就是说,资源的状态包括关于所有现有资源的知识。/objects/object/{id}
  3. /objects资源处理POST请求时,它必须 a)/object/{id}根据请求负载中传递的数据创建新资源;b) 通过添加有关新创建资源的数据来修改其自身状态。
  4. /object/{id}当要创建的资源具有重复的标识符时,即已存在具有相同 URI 的资源时,该/objects资源将无法处理POST请求,因为其状态已包含重复的/object/{id}URI。

这正是状态码描述中提到的与目标资源当前状态的冲突409 Conflict


p0l*_*ear 26

可能在游戏后期,但我在尝试制作REST API时偶然发现了这个语义问题.

为了扩展Wrikken的答案,我认为您可以使用409 Conflict403 Forbidden根据具体情况 - 简而言之,当用户无法解决冲突并完成请求时(例如他们无法发送请求)时,请使用403错误DELETE请求显式删除资源),或者如果可能的话可以使用409.

10.4.4 403禁止

服务器理解请求,但拒绝履行请求.授权无效,请求不应重复.如果请求方法不是HEAD并且服务器希望公开为什么请求没有得到满足,那么它应该描述实体中拒绝的原因.如果服务器不希望将此信息提供给客户端,则可以使用状态代码404(未找到).

如今,有人说"403"并且出现了权限或身份验证问题,但规范说它基本上是服务器告诉客户端它不会这样做,不要再问它,这就是为什么客户端不应该"T.

至于PUTvs. POST... POST应该用于在用户无法或不应该为资源创建标识符时创建资源的新实例.PUT在知道资源的标识时使用.

9.6 PUT

...

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

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

  • 我认为*403 Forbidden*意味着,即使用户经过*身份验证*,他也不会*授权*执行请求的操作.我不会将它用于验证错误.**示例**:未登录,我尝试删除某些内容.服务器发给我*401 Unauthorized*(这个名字很糟糕,应该是*401 Unauthenticated*).我登录后再试一次.这次服务器检查我的权限,看到我不被允许并返回*403 Forbidden*.另见[this question](http://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses). (7认同)
  • 根据规范,暗示错误409不能通过`POST`请求​​(正确使用时)返回,因为它声明它应该在与_eject resource_冲突时返回.由于目标资源尚未发布,因此无法发生冲突,因此回复"409 Conflict"没有任何意义. (2认同)

Sła*_*art 19

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

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

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

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

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

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

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

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

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

  • 所有 4xx 错误都是客户端的“错误”。所有 5xx 错误都是服务器的“错误”。(提交重复数据是客户端必须解决的问题,而不是服务器。) (12认同)
  • 请求不是复制资源,而是将数据附加到一个资源。在我看来,你的答案是最好的。 (4认同)

ala*_*jds 14

"302 Found"听起来合情合理.并且RFC 2616说它可以回答除GET和HEAD之外的其他请求(这肯定包括POST)

但是它仍然让访问者通过这个URL来获取RFC的"Found"资源.为了使它直接进入真正的"找到"URL,应该使用"303 See Other",这是有道理的,但强制另一个调用GET以下的URL.好的方面,这个GET是可缓存的.

我想我会用"303见其他".我不知道我是否可以回复身体中发现的"东西",但我想这样做是为了将一次往返保存到服务器上.

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

  • 3xx状态用于重定向 (82认同)
  • 恕我直言,“307临时重定向”是真正的临时重定向。“302”是不明确的,但是“FOUND!!” 是这里真正想要的消息。最好的明确妥协是 HTTP 语义上的“303 See Other”。**我会选择“303 See Other”。** (2认同)
  • @DavidVartanian 感谢您的讨论。**更新了 409 的答案**。客户要求不可能的东西是错误的,即使它不知道这是不可能的。 (2认同)

Alf*_*nda 11

我认为你不应该这样做.

如您所知,POST是修改集合的,它用于创建新项目.所以,如果你发送id(我认为这不是一个好主意),你应该修改集合,即修改项目,但这是令人困惑的.

用它来添加一个没有id的项目.这是最好的做法.

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

  • 忘记数据库表.假设一个产品只能与一个帐户相关......那么它就是一对多的关系.所以,POST/product/{id}与{'account':account_id}.如果你将最大基数设置为'1'(一对一的关系)....为什么它们分开了休息对象?基数错误只是400错误.把事情简单化.我希望我理解你的问题. (2认同)

Abd*_*leh 8

在您的情况下,您可以使用 409 Conflict

如果你想HTTPs从下面的列表中检查另一个状态代码

1×× 信息

100 Continue
101 Switching Protocols
102 Processing
Run Code Online (Sandbox Code Playgroud)

2×× 成功

200 OK
201 Created
202 Accepted
203 Non-authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status
208 Already Reported
226 IM Used
Run Code Online (Sandbox Code Playgroud)

3×× 重定向

300 Multiple Choices
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
307 Temporary Redirect
308 Permanent Redirect
Run Code Online (Sandbox Code Playgroud)

4×× 客户端错误

400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Timeout
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Payload Too Large
414 Request-URI Too Long
415 Unsupported Media Type
416 Requested Range Not Satisfiable
417 Expectation Failed
418 I’m a teapot
421 Misdirected Request
422 Unprocessable Entity
423 Locked
424 Failed Dependency
426 Upgrade Required
428 Precondition Required
429 Too Many Requests
431 Request Header Fields Too Large
444 Connection Closed Without Response
451 Unavailable For Legal Reasons
499 Client Closed Request
Run Code Online (Sandbox Code Playgroud)

5×× 服务器错误

500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
505 HTTP Version Not Supported
506 Variant Also Negotiates
507 Insufficient Storage
508 Loop Detected
510 Not Extended
511 Network Authentication Required
599 Network Connect Timeout Error
Run Code Online (Sandbox Code Playgroud)


Sin*_*tic 7

我认为对于REST,你只需要对该特定系统的行为做出决定,在这种情况下,我认为"正确"的答案将是这里给出的几个答案之一.如果您希望请求停止并且行为就好像客户端在继续之前犯了一个需要修复的错误,那么使用409.如果冲突真的不那么重要并且想要保持请求继续,那么通过重定向来响应客户端到找到的实体.我认为正确的REST API应该在POST之后重定向(或者至少提供位置头)到该资源的GET端点,因此这种行为将提供一致的体验.

编辑:值得注意的是,您应该考虑PUT,因为您提供了ID.然后行为很简单:"我不在乎现在有什么,把这个东西放在那里." 意思是,如果没有任何东西,它就会被创造出来; 如果有东西它会被替换.我认为当服务器管理该ID时,POST更合适.分离这两个概念基本上告诉你如何处理它(即PUT是幂等的,所以只要有效载荷有效,它总是有效,POST总是创建,所以如果有ID冲突,那么409会描述这个冲突) .


Gra*_*zan 6

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

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

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

对于记录,422也是当您尝试使用已在使用的名称创建存储库时GitHub使用的状态代码.

  • @rwenz3l 为什么不呢?它相当传统,明确符合目的,并且传达了它的意图。 (4认同)
  • 422 是 webdav 规范,所以我不建议将其用于 REST API (2认同)

Phi*_*ton 5

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

来自10个状态码定义:

"202已接受.该请求已被接受处理,但处理尚未完成."

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

我倾向于投掷一个202,并返回类似的内容到GET /{resource}/{id}会返回的内容.

  • 这个答案是对的.202表示服务器未发现请求有问题,但选择在响应后处理请求.这也意味着它希望处理成功.在我们的例子中,服务器知道处理将失败,因此202是错误的响应. (17认同)
  • 202的示例是队列或订阅。换句话说,如果您现在要查询请求的结果,则可能无法立即获得该结果。 (4认同)
  • @Adrian 和 lucastamoios 我认为你们都假设服务器在提供响应之前同步从数据库中读取。情况并非总是如此,因此这个答案并不是“错误”,因为服务器并不总是“了解”现有记录。在异步系统中,这种情况很常见,其中 api 层只是记录请求以供后台工作人员处理。 (2认同)