使用POST创建请求,响应代码为200或201且包含内容。

194
假设我编写了一个REST服务,其意图是向系统添加新的数据项。
我计划使用POST方法发送请求到:
http://myhost/serviceX/someResources

假设操作成功,我应该使用哪个响应代码?并且应该返回什么内容。
我正在查看HTTP响应代码的定义,可以选择以下可能性:
200:返回描述或包含操作结果的实体;
201:表示已创建。意味着请求已被满足,并导致创建了一个新资源。新创建的资源可以通过响应实体中返回的URI引用,其中最具体的资源由位置标头字段给出。响应应包括包含用户或用户代理可以选择最合适的资源特征和位置列表的实体。实体格式由Content-Type标头字段中给定的媒体类型指定。
后者更符合Http规范,但我不清楚“响应应包括包含资源特征和位置列表的实体”是什么意思。
有什么建议?解释?
7个回答

139
这个想法是,响应体会给你一个页面,页面上有一个链接指向这个东西:
201 Created
状态码 201 (已创建) 表示请求已被满足,并且已创建了一个或多个新资源。请求创建的主要资源由响应中的 Location 头字段标识,如果没有收到 Location 字段,则由有效的请求 URI 标识。
这意味着你需要在响应的头部包含一个 Location,它给出了新创建的东西的 URL。
HTTP/1.1 201 Created
Date: Sat, 02 Apr 2016 12:22:40 GMT
Location: https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586

响应体

然后他们继续提到响应体中应该包含的内容:

201 响应负载通常描述并链接到已创建的资源。

对于使用浏览器的用户,您可以提供一个他们可以查看和点击的内容,以便访问他们新创建的资源:

HTTP/1.1 201 Created
Date: Sat, 02 Apr 2016 12:22:40 GMT
Location: https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586
Content-Type: text/html

Your answer has been saved! 
Click <A href="/a/36373586/12597">here</A> to view it.

如果页面只会被机器人使用,那么让响应内容能够被计算机读取是有意义的。
HTTP/1.1 201 Created
Date: Sat, 02 Apr 2016 12:22:40 GMT
Location: https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586
Content-Type: application/xml

<createdResources>
   <questionID>1860645</questionID>
   <answerID>36373586</answerID>
   <primary>/a/36373586/12597</primary>
   <additional>
      <resource>https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586</resource>
      <resource>https://dev59.com/SHI-5IYBdhLWcg3wbXtN#1962757</resource>
   </additional>
</createdResource>

或者,如果你喜欢的话:
HTTP/1.1 201 Created
Date: Sat, 02 Apr 2016 12:22:40 GMT
Location: https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586
Content-Type: application/json

{ 
   "questionID": 1860645, 
   "answerID": 36373586,
   "primary": "/a/36373586/12597",
   "additional": [
      "https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586",
      "https://dev59.com/SHI-5IYBdhLWcg3wbXtN#36373586"
   ]
}

对于这个问题的回答完全取决于你,你可以随意选择。

缓存友好

最后,我可以对已创建的资源进行预缓存优化(因为我已经拥有了内容;我只是上传了它)。服务器可以返回一个日期或ETag,我可以将其与刚刚上传的内容一起存储:

请参阅第7.2节,了解验证器头字段(如ETagLast-Modified)在201响应中的含义和目的。

HTTP/1.1 201 Created
Date: Sat, 02 Apr 2016 12:22:40 GMT
Location: https://dev59.com/SHI-5IYBdhLWcg3wbXtN#23704283
Content-Type: text/html
ETag: JF2CA53BOMQGU5LTOQQGC3RAMV4GC3LQNRSS4
Last-Modified: Sat, 02 Apr 2016 12:22:39 GMT 

Your answer has been saved! 
Click <A href="/a/36373586/12597">here</A> to view it.

而且ETag是纯粹的任意值。当资源发生变化时(并且需要更新缓存),它们的不同是唯一重要的。通常ETag是一个哈希值(例如SHA2-256)。但它也可以是数据库的rowversion,或者是递增的修订号。任何在事物发生变化时会改变的东西。

到目前为止,您的回复似乎是最明智的。我有点担心回复的本体论,但除此之外,它似乎是规范的最成熟的阐释。我很好奇是否有任何一种轻量级的“响应式”方式来处理人类/机器输出。但我最感兴趣的是您建议的“缓存自己的输入”。我知道的大多数Web应用程序都不会创建资源的1:1版本,即使这是一些微不足道的事情,比如规范化字符串的大小写。把提交的版本视为ETag创建的版本是否有点风险? - Anthony
1
@Anthony,缓存:它可以是一种1:1文件存储应用程序。例如比较WebDAV PUT和POST。需要处理大型文件。 - kxr
@Anthony 如果你想向客户端返回一个ETag,那就由你决定。如果客户端刚上传的内容与你保存的不一致,那就不要返回ETag。这是你的灵活性和选择。 - Ian Boyd
你的响应为什么缺少Content-Length? - Vinnie Falco
1
@VinnieFalco 这是关于201响应代码的答案。为了说明目的,Content-Length已省略。 - Ian Boyd

124

简单来说:

  • 200 表示创建一个对象后,该对象已被创建并返回
  • 201 表示创建一个对象后,只返回其引用(例如ID或链接)

5
这个的出处是什么? - sudo soul
4
好的,我会尽力以通俗易懂的方式翻译以下内容,但不会更改原意或添加任何额外解释。以下是我从 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 和 https://httpstatuses.com/201 中了解到的内容: - Stéphane Bruckert
14
阅读完 https://tools.ietf.org/html/rfc7231#section-6.3.1 后,我同意这个理解 - 我想更多地了解你是如何得出这个理解的。但现在我理解为...200 = 资源已创建并返回 | 201 = 资源已创建并返回引用 | 204 = 资源已创建,未返回有效载荷 - sudo soul
@sudosoul 如果它在201中,那么位置标头是否也会随着204一起返回? - pmiguelpinto
2
根据RFC 7231,我认为在返回204响应时不应返回位置头。尽管如此,204响应可以包含头部元数据,最终暗示请求成功。请查看我发布的RFC 7231链接,并查看关于204的段落。 - sudo soul

94

我认为AtomPub REST API是一个很好的RESTful服务的例子。请看下面从AtomPub规范中摘录的代码:

POST /edit/ HTTP/1.1
Host: example.org
User-Agent: Thingio/1.0
Authorization: Basic ZGFmZnk6c2VjZXJldA==
Content-Type: application/atom+xml;type=entry
Content-Length: nnn
Slug: First Post

<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <title>Atom-Powered Robots Run Amok</title>
  <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
  <updated>2003-12-13T18:30:02Z</updated>
  <author><name>John Doe</name></author>
  <content>Some text.</content>
</entry>
服务器返回201状态码以表示创建成功。响应包含一个 Location 头部,指示 Atom Entry 的 Member Entry URI,并在响应正文中包含该条目的表示。

服务器信号以状态代码201成功创建。响应包括Location头,指示Atom条目的成员入口URI,以及响应正文中该条目的表示。

HTTP/1.1 201 Created
Date: Fri, 7 Oct 2005 17:17:11 GMT
Content-Length: nnn
Content-Type: application/atom+xml;type=entry;charset="utf-8"
Location: http://example.org/edit/first-post.atom
ETag: "c180de84f991g8"  

<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
  <title>Atom-Powered Robots Run Amok</title>
  <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
  <updated>2003-12-13T18:30:02Z</updated>
  <author><name>John Doe</name></author>
  <content>Some text.</content>
  <link rel="edit"
      href="http://example.org/edit/first-post.atom"/>
</entry>

由 Collection 创建并返回的 Entry 可能与客户端 POST 的 Entry 不匹配。服务器可能会更改 Entry 中各个元素的值,例如 atom:id、atom:updated 和 atom:author 值,并可以选择删除或添加其他元素和属性,或更改元素内容和属性值。


9
如果资源的大小达到了几个GB,那么返回已创建的资源可能有些过于繁琐。 - Tor Valamo
12
同意!这就是必要的优化——但你不想过早地去做它。重要的是以Restful的精神进行设计,只有在必要时才做出例外。 - Chandra Patni
4
@ChandraPatni,"Atom is dead"的意思是需要更好的例子。 - Pacerier
16
虽然 Atom 可能已经过时了,但这个例子的精神依然是非常准确的。 - Ashimema
2
我对201响应的最初解释更像是“嘿,你想创建一个资源,但基于上下文,你可能对最终结果不感兴趣,或者只有写入访问权限而没有读取访问权限。在任何情况下,在返回主集合之前,你所需要的就是已创建资源的URL作为证明。”除此之外,似乎没有其他内容需要响应200。除非RFC有其他考虑。 - Anthony
显示剩余3条评论

39

查看HTTP:方法定义:POST

通过POST方法执行的操作可能不会导致可以由URI标识的资源。在这种情况下,响应状态应该是200(OK)或204(No Content),这取决于响应是否包含描述结果的实体。

如果在原始服务器上创建了资源,则响应应该是201(Created),并包含一个描述请求状态并引用新资源的实体以及一个位置头(参见第14.30节)。


18

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19

这只是一个用冒号分隔的键值对。

ETag: "xyzzy"

它可以是任何类型的文本数据 - 通常我会包含一个带有创建项目标识符的 JSON 字符串。仅仅因为方便测试,包含它就很值得。

ETag: "{ id: 1234, uri: 'http://domain.com/comments/1234', type: 'comment' }"

在这个例子中,所创建的项目的标识符、URI和类型是"资源特征和位置"。

3
您在说ETag对应于一个包含资源特征和位置列表的实体。我认为您的建议很好,非常赞同您关于测试的观点。但是我不明白这与“资源特征和位置列表”有什么关系。 - djna
7
具体指出问题,让人们可以学习。否则,这只是一种挥手示意而已。 - tempire
@SimonGibbs 有什么问题? - MEMark
2
虽然它严格遵循规范,但它推荐了一种非常不寻常的实现选项。此外,它实际上并没有回答页面顶部的问题(或者通过混淆ETag和实体这些词来回答)。得到43票的答案可能更好。 - Simon Gibbs
规范中并未列出该意图;如果不是为了意图,那么规范的作用是什么? 使用它来表示“上次修改时间”可能是惯例,但规范要更加灵活。 - tempire
显示剩余6条评论

1
实际输出取决于所请求的内容类型。但是,最少应将创建的资源放置在位置中。就像发布-重定向-获取模式一样。在我的情况下,除非另有要求,否则我会将其留空。因为这是使用Response.created()时JAX-RS的行为。但请注意,浏览器和Angular等框架不会自动遵循201。我已经在http://www.trajano.net/2013/05/201-created-with-angular-resource/中记录了这种行为。

-1

对于这个问题,我会给出另一个答案,那就是采取务实的方法,保持你的REST API contract简单。在我的情况下,我重构了我的REST API,使得测试更加容易,而不需要使用JavaScript或XHR,只需使用简单的HTML表单和链接。

所以,针对你上面的问题,我会使用返回码200,并且返回的消息包含一个JSON消息,你的应用程序可以理解。根据你的需求,它可能需要新创建的对象的ID,以便Web应用程序可以通过另一个调用获取数据。

需要注意的是,在我重构的API合同中,POST响应不应包含任何可缓存的数据,因为POST请求实际上是不可缓存的,所以将其限制为可以使用GET请求请求和缓存的ID。


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