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

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

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

PUT /objects/{id} HTTP/1.1...
{json representation of the object}

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

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

POST /objects/ HTTP/1.1...
{json representation of the object, including ID}

由于POST的意思是“追加”操作,我不知道如果对象已经在那里该怎么办。我应该将请求视为修改请求还是应该返回一些错误代码(哪个)?

653144 次浏览

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

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

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

我个人使用WebDAV扩展422 Unprocessable Entity

根据RFC 4918

422 Unprocessable Entity状态码表示服务器理解请求实体的内容类型(因此415 Unsupported Media Type状态码不合适),请求实体的语法是正确的(因此400 Bad Request状态码不合适),但无法处理包含的指令。

“302找到”对我来说听起来很合理。rfc2616表示它可以回答GET和HEAD以外的其他请求(这当然包括POST)

但它仍然让访问者通过RFC访问此URL以获取此“找到”资源。要使其直接访问真正的“找到”URL,应该使用“303查看其他”,这是有道理的,但强制另一个调用GET其以下URL。从好的方面来看,这个GET是可缓存的。

我认为我会用“303看到其他”。我不知道我是否可以用身体中发现的“东西”来回应,但我想这样做可以节省一次往返服务器的时间。

更新:重新阅读RFC后,我仍然认为不存在“4XX+303找到”代码应该是正确的。然而,“409冲突”是现有的最佳答案代码(如@Wrikken所指出的),可能包括指向现有资源的位置标头。

我不认为你应该这么做。

如你所知,POST是用来修改集合的,它用于创建一个新项目。所以,如果你发送id(我认为这不是一个好主意),你应该修改集合,即修改项目,但这很混乱。

使用它来添加一个项目,没有id。这是最佳实践。

如果你想捕获一个唯一的约束(不是id),你可以响应409,就像在PUT请求中一样。但不是ID。

208-0怎么样?这是一个选择吗?

在我看来,如果唯一的东西是重复资源,则不应该引发错误。毕竟,无论在客户端还是服务器端都没有错误。

另一种潜在的治疗方法是使用PATCH。PATCH被定义为改变内部状态的东西,并且不限于附加。

PATCH将通过允许您更新现有项目来解决问题。请参阅:RFC 5789:补丁

游戏可能很晚,但我在尝试制作REST API时偶然发现了这个语义学问题。

为了扩展一下Wrikken的回答,我认为你可以根据情况使用409 Conflict403 Forbidden——简而言之,当用户完全无法解决冲突并完成请求时,使用403错误(例如,他们不能发送DELETE请求以显式删除资源),或者如果可以做些什么,则使用409。

10.4.4 403禁止

服务器理解请求,但拒绝满足它。授权无济于事,请求不应重复。如果请求方法不是HEAD,服务器希望公开为什么请求没有得到满足,它应该描述原因对于实体中的拒绝。如果服务器不希望进行此信息可供客户端使用,状态码404(不是找到)可以代替使用。

如今,有人说“403”,就会想到权限或身份验证问题,但规范说,它基本上是服务器告诉客户端它不会这样做,不要再问了,这就是为什么客户端不应该这样做。

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

9.6 PUT

POST和PUT请求之间的根本区别是反映在请求URI的不同含义中。a中的URIPOST请求标识将处理封闭的资源实体。该资源可能是一个数据接受过程,一个通往一些其他协议,或接受注释的单独实体。在相反,PUT请求中的URI标识包含在请求--用户代理知道要使用的URI和服务器不得尝试将请求应用于其他资源。如果服务器希望将请求应用于不同的URI,

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

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

编辑:还值得注意的是,由于提供了ID,你应该考虑使用PUT。然后行为很简单:“我不在乎现在有什么,把这个放在那里。”这意味着,如果没有东西在那里,它将被创建;如果有东西在那里,它将被替换。我认为当服务器管理该ID时,POST更合适。将这两个概念分开基本上告诉你如何处理它(即PUT是幂等的,所以只要有效负载有效,它应该总是工作,POST总是创建,所以如果ID发生冲突,那么409将描述这种冲突)。

根据rfc7231,如果处理POST的结果相当于表示现有资源。

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

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

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

https://www.rfc-editor.org/rfc/rfc7231#section-6.4.1

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

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

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

为了记录在案,422也是GitHub在您尝试通过已在使用的名称创建存储库时使用的状态代码。

更有可能是400 Bad Request

[**6.5.1.400错误请求**][1]


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

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

这都是关于<说明>上下文的,还有谁负责处理请求的副本(服务器或客户端或两者兼而有之)


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

  • 400错误请求-当服务器不会处理请求时,因为它是明显的客户端错误
  • 409冲突-如果服务器不处理请求,但原因不是客户端的错

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

  • 200 OK
  • 201创建

如果服务器是期望返回一些东西,请查看3XX:

  • 302找到了
  • 303查看其他

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


如果以上还不够,那么在响应的主体中准备一些错误消息总是一个很好的做法。

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

如果您想检查下面列表中的其他HTTPs状态代码

1××信息

100 Continue101 Switching Protocols102 Processing

2××成功

200 OK201 Created202 Accepted203 Non-authoritative Information204 No Content205 Reset Content206 Partial Content207 Multi-Status208 Already Reported226 IM Used

3××重定向

300 Multiple Choices301 Moved Permanently302 Found303 See Other304 Not Modified305 Use Proxy307 Temporary Redirect308 Permanent Redirect

4××客户端错误

400 Bad Request401 Unauthorized402 Payment Required403 Forbidden404 Not Found405 Method Not Allowed406 Not Acceptable407 Proxy Authentication Required408 Request Timeout409 Conflict410 Gone411 Length Required412 Precondition Failed413 Payload Too Large414 Request-URI Too Long415 Unsupported Media Type416 Requested Range Not Satisfiable417 Expectation Failed418 I’m a teapot421 Misdirected Request422 Unprocessable Entity423 Locked424 Failed Dependency426 Upgrade Required428 Precondition Required429 Too Many Requests431 Request Header Fields Too Large444 Connection Closed Without Response451 Unavailable For Legal Reasons499 Client Closed Request

5××服务器错误

500 Internal Server Error501 Not Implemented502 Bad Gateway503 Service Unavailable504 Gateway Timeout505 HTTP Version Not Supported506 Variant Also Negotiates507 Insufficient Storage508 Loop Detected510 Not Extended511 Network Authentication Required599 Network Connect Timeout Error

这是用户端故障,属于4xx组。这是正确答案https://developers.rebrandly.com/docs/403-already-exists-errors

错误402,需要付款

也就是说,这个资源已经存在,但如果你给我足够的钱,我会删除当前的资源并把它给你:D

…但是看看mozilla对状态码的定义https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses

作为一个更严肃的答案,没有人在这里提供,那么451呢:出于法律原因不可用。您不能“合法地(根据您自己制定的条款和条件)”让多人访问相同的帐户信息

422也是一个很好的选择,它是不可处理的实体该请求是格式良好的,但由于语义错误而无法跟踪。因为它是一个完全有效的请求,但由于它在语义上等于另一个条目,因此无法跟踪。

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

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

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

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

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状态代码描述中提到的与目标资源当前状态的冲突。

既然你提到使用post的对象创建请求包含对象的ID,你应该将其设为幂等请求。只需返回与成功创建请求完全相同的响应。幂等请求使API更简单,例如。现在客户端不必担心2种不同的情况(成功,失败)。或者客户端可以安全地重试请求,以防连接/服务器暂时关闭出现问题。