使用PUT方法时,如何处理不完整的表述。

10
什么是在资源的不完整表示下期望的PUT标准行为?
例如,我有一个位于/api/users/1的User,由下面的HAL json表示:
{'id': 1,
 'username': 'joedoe',
 'email': 'joe@doe.com',
 'password_hash': '9039dmk38f84uf4029i339kf32f0932i',
 'last_visit': '2013-11-04 21:09:01',
 'public': true,
 '_links': {'self': {'href': 'http://foo.bar.com/api/users/1'}}

}

然后,我发起PUT请求来更改用户名电子邮件,使用一个表示中没有其他属性的内容:
PUT /api/users/1

{'username': 'joeydoey',
 'email': 'joey@doey.com'}

到目前为止,我一直认为这应该被视为错误,因为它意味着部分更新,但this answer让我开始思考,说一个不完整的表示仍然是一个完整的替换,服务器可以用默认值填充空白。我在HTTP标准中找不到任何与此相关的内容,所以我必须问一下,在这种情况下,期望的标准化行为是什么?
  1. 应该会出现错误,因为它意味着部分更新。PUT负载的模式应与使用相同资源和媒体类型的GET检索到的模式完全相同。

  2. 应该成功,因为服务器可以用该媒体类型的默认值填写空白。在这种情况下,它将重置密码为空白或默认密码,并相应地刷新哈希,然后将last_visit和public值设置为默认值。当考虑HATEOAS时,如果客户端提交与服务器返回的相同媒体类型,则此选项更有意义,因为它无法预测超媒体控件将如何更改,表示每次客户端未发送所有超链接时必然不完整,因此服务器必须相应地重置它们。

  3. 1和2都有效,因为没有标准化行为,而是由媒体类型决定如何处理它。这感觉不对,因为PUT不是从属于资源本身,而是替换它。

请记住,我不是在问什么感觉对或有什么意义。我正在询问哪一个得到了标准支持。


作为一个对此很新的人来说,听起来你是在询问是否可以重载PUT来用作PATCH?请参考http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/和http://tools.ietf.org/html/rfc5789。 - The Tahaan
不是这个意思。 - Pedro Werneck
2个回答

5
只要客户端对资源的理解是PUT的结果是完全替换(即,没有传递的属性的先前值不会影响它们在PUT后的值),PUT就应该成功。然而,这确实让人感到有些困惑,因为很多人倾向于将PUT的这种使用与字段级更新语义混淆(而不是完全替换)。
虽然在这里技术上没有违反REST约束,但最好传入所有值,而不是诉诸于服务器默认值,因为这将有助于保持向前兼容性。默认值可能随时间而变化,因此通常应避免使用它们。
然而,你提到的链接示例并不是指不传递默认值,因此它不是“不完整表示”的好例子。相反,链接不是客户端对资源到服务器的表示的一部分。我认为你在这里引入了另一个概念:仅从服务器返回给客户端的属性。这就是我在其他帖子中谈论的内容,这也是引发本篇文章的线程的话题。
我所说的不是不完整的表示;它是一种不同的表示。你实际上正在处理两种描述相同资源的不同媒体类型(即表示)。一个来自客户端(我们称之为application/vnd.example.api.client),另一个来自服务器(application/vnd.example.api.server)。它们可能没有明确标记,但至少隐含着这种情况。因此,由于它们是两种不同的媒体类型,它们表达了关于相同资源的不同内容。
既然你提到了HAL,请考虑客户端通常不会向服务器POST一个application/hal+json格式的消息。查看HALTalk中的注册rel作为示例。预期的内容类型是application/json,而不是application/hal+json。如果你查看示例post,它与HAL无关。没有链接,没有嵌入对象等。但是......如果你GET从此POST返回的Location头部返回的URL,假设你的客户端接受JSON上的HAL,则返回一个类型为application/hal+json的响应(即带有链接的用户)。两种不同的媒体类型,相同资源的两种不同表示。
让我用Accept和Content-Type头来装饰您的示例,以阐明我的观点。 请求
PUT /api/users/1 HTTP/1.1
Content-Type: application/vnd.example.api.client+json

{'username': 'joeydoey',
 'email': 'joey@doey.com'}

响应

200 OK
Content-Type: application/hal+json;profile=application/vnd.example.api.server

{'id': 1,
 'username': 'joeydoey',
 'email': 'joey@doey.com',
 'password_hash': '9039dmk38f84uf4029i339kf32f0932i',
 '_links': {'self': {'href': 'http://foo.bar.com/api/users/1'}}
}

大多数系统不会详细描述它们的媒体类型。通常它只是以更通用的类型(如application/json或仅一个自定义媒体类型)为框架。然而,这并不改变它们是两种不同表示形式的同一基础资源的事实。明白吗?

我没有错误地提出问题。我确实意味着不完整的表示。你所说的是正确的,但问题依然存在。即使我有不同的媒体类型来检索和提交资源表示,当表示缺少一些可以通过默认值填充的数据时,服务器应该怎么做? - Pedro Werneck
啊,好的。你提到客户端没有传递链接的例子让我有些困惑。你可以通过从第二点中删除HATEOAS的猜测并添加更多明显可默认的字段(例如关于联系人偏好的布尔字段)来使其更清晰。 - Jonathan W
我不确定HATEOAS的猜测是否太过格格不入。在使用XML时比使用HAL+Json时不那么明显。媒体类型两种方式都是相同的,但你会遗漏一些参数。我按照你的建议添加了一些字段。 - Pedro Werneck
1
抱歉,但那并不是真的。媒体类型在两个方向上并不相同。从服务器到客户端是application/hal+json(带有导航链接),但从客户端到服务器是application/json(没有它们)。我想不出任何情况下你会想让客户端将导航链接传递给服务器。 - Jonathan W
当然,这正是重点。这就是hal+json的情况,其中有一个明显的媒体类型用于检索和提交,但对于其他媒体类型(例如atom+xml),可能并不那么明显。 - Pedro Werneck
并非每个链接都是导航链接。链接也可以是关系,例如“集合<->项”关系或具有特定类别的文档,或更新作者。对我来说,这些可以表示为链接(关系),并且可以由客户端控制。我们双向使用HAL。 - Evert

0

PUT 方法用于替换。服务器可以修改/增加数据,但最终的表示应该是 payload 的函数,而不是最终状态的函数。

在您的示例中,密码哈希似乎不是服务器可以填充的内容,对吗?在这种情况下,PUT 应该会导致错误。


这不是我要问的。请仔细阅读问题。 - Pedro Werneck

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