HTTP PUT是否应该在资源不存在时创建资源?

36
假设有人在我的端点上执行了一个PUT请求:
/resources/{id}

然而,我的PostgreSQL数据库中没有存储给定ID的资源。
根据RFC 2616 ,如果有能力,应创建该资源:
“PUT”方法请求在提供的Request-URI下存储所包含的实体。如果Request-URI引用已经存在的资源,则应将所包含的实体视为修改版本,该版本停留在原始服务器上。如果Request-URI不指向现有资源,并且用户代理可以将该URI定义为新资源,则源服务器可以使用该URI创建资源。
是否可以使用提供的ID创建资源?因为在数据库插入时手动分配ID并不是最佳实践。
如果无法创建资源,我应该返回404错误吗?

1
也许你的问题有两个方面:
  1. PUT 方法是否可以创建不存在的资源。
  2. 生成数据库 ID 应该使用什么方案。
我认为回答第二个问题(自己)是决定 RFC 给出的选项的关键。
- kosgeinsky
4个回答

66

首先,您正在使用过时的文档:RFC 2616现在已不再相关,任何使用此文档作为参考的人应该立即停止。

引用马克·诺丁汉(Mark Nottingham)的话,在撰写本文时,他是IETF HTTP和QUIC工作组的联合主席:

不要使用RFC2616。从硬盘、书签中删除它,并烧掉(或负责地回收)任何已打印出来的副本。

旧的RFC 2616已被以下文件取代,这些文件共同定义了HTTP/1.1协议:

如果你正在寻找方法、状态码和头部定义,那么你应该参考RFC 7231这份文档。


说了这么多,让我们回到你的问题。

如果资源不存在,HTTP PUT 应该创建它吗?

这取决于情况。

但是,如果您的应用程序代表客户端生成资源标识符,就像您在问题中提到的那样,那么您应该使用POST而不是PUT来创建资源。

以下是PUT方法定义的一些部分。最后一句似乎与您最相关(我突出了重点),支持我刚才提到的内容:

4.3.4. PUT PUT方法请求使用请求消息有效载荷中封装的表示定义的状态创建或替换目标资源的状态。 [...]
如果目标资源没有当前表示并且PUT成功创建了一个,则原始服务器必须通过发送201(已创建)响应来通知用户代理。如果目标资源具有当前表示并且根据封闭表示的状态成功修改该表示,则原始服务器必须发送200(OK)或204(无内容)响应以指示请求的成功完成。[...]
正确解释PUT请求假定用户代理知道所需的目标资源。在接收到更改状态的请求后,代表客户端选择适当的URI的服务应使用POST方法而不是PUT[...]
如果创建资源不可能,我应该返回404错误吗?这似乎是一个准确的状态码,因为没有找到所请求的资源的表示形式: 6.5.4. 404 Not Found 404(未找到)状态代码表示源服务器未找到目标资源的当前表示形式或不愿透露存在该资源。[...]
现在,为了完整起见,请查看以下关于POST方法定义的相关引用,该方法应在您问题描述的情况下用于创建资源:

4.3.3. POST

POST方法请求目标资源根据资源自己的特定语义处理请求中包含的表示。例如,POST用于以下功能(等等):

[...]

  • 创建一个尚未被源服务器识别的新资源;

[...]

如果作为成功处理POST请求的结果在源服务器上创建了一个或多个资源,则源服务器应发送包含Location头字段的201(已创建)响应,该字段提供主要已创建的资源的标识符并引用新资源的状态。

201状态码表示新资源已创建时,Location头部指示新创建的资源的位置。如果未提供Location头,则客户端应假定资源由有效请求URI标识:

6.3.2. 201 Created

201(已创建)状态码表示请求已被满足并导致创建一个或多个新资源。请求创建的主要资源通过响应中的Location头字段标识,如果未收到Location字段,则通过有效请求URI标识。[...]


4
为了保持本答案的高技术水平:本答案提到的所有RFC文档都已经被废弃。 RFC-9110取代了以下RFC文档:RFC-7230、RFC-7231、RFC-7232、RFC-7233和RFC-7235。 RFC-9111取代了以下RFC文档:RFC-7234。 - mochomecha
如果我们找不到要更新的项目,难道不应该返回204无内容吗? - Molemann

3
简而言之,这取决于您想要存储的有效载荷是否违反了服务器对资源的任何约束条件。
一般来说,我会建议尝试将其存储到目标URI上,因为客户端明确表达了他的意图要将特定表示存储到该URI上。但是在此之前,服务器应该执行约束检查!通常情况下,在真实的REST场景中,客户端应该使用由服务器提供的URI,而不仅仅是随意选择任何URI。因此,服务器应该控制其名称空间,因此默认情况下不建议使用PUT来创建资源。
说到这里,由于PUT是幂等的而POST不是,一些客户端可能希望从中受益。在这里, POST-PUT创建模式已经发展出来,其中客户端尝试通过POST创建新资源,直到收到响应中的Location头确认,然后尝试通过PUT更新该资源的状态。这样,客户端可以确保在传输问题的情况下,表示仅被创建一次。根据立场,有些人可能认为实际更新资源是实际的资源创建,但是由于客户端事先收到了相应的链接,这并不完全是这种情况。
请注意,如果服务器配置为为某些URI端点提供特定表示,则服务器也有权将表示转换为其他内容。想象一下通过PUT上传图像到URI并且服务器将图像嵌入HTML页面。

1
这里有两个问题:1)PUT请求是否应该尝试创建资源;2)如果无法创建,会发生什么。
1)@cass链接的RFC文档https://www.rfc-editor.org/rfc/rfc7231#section-4.3.4中说:
PUT方法请求使用请求消息有效负载中的表示定义的状态创建或替换目标资源的状态。成功的PUT操作将表明对同一目标资源的后续GET将导致在200(OK)响应中发送等效表示。
此外,Mozilla的文本https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT
HTTP PUT请求方法创建一个新资源或使用请求有效负载替换目标资源的表示。
进一步地,在原始RFC文档(已被上述测试替换)https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html中:
PUT方法请求将封装的实体存储在提供的请求URI下。如果请求URI引用已经存在的资源,则应将封装的实体视为驻留在源服务器上的实体的修改版本。如果请求URI不指向现有资源,并且用户代理可以将该URI定义为新资源,则源服务器可以使用该URI创建资源。
这有点传闻,但Kubernetes API也仔细区分了这一点,并向其用户介绍了PATCH,如果他们确实意味着更新:https://kubernetes.io/docs/reference/using-api/api-concepts/#api-verbs
对于PUT请求,Kubernetes根据现有对象的状态将其内部分类为创建或更新。更新与修补不同;用于修补的HTTP动词是PATCH。
2)就“失败会发生什么”而言,我认为代码取决于出了什么问题:
400:由于负载不良而无法创建。 409:由于某些输入JSON中的字段具有某些全局唯一性检查,因此无法创建。 502/3:由于尝试调用数据库时它已死亡,因此无法创建。
我不确定404是否是最好的代码,因为它不告诉用户关于“为什么”的任何信息。

0
根据RFC 9110,2023年的情况是,PUT方法可以创建或替换资源。
9.3.4. PUT PUT方法要求使用请求消息内容中所包含的表示定义的状态来创建或替换目标资源的状态。

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