REST API应该如何处理对部分可修改资源的PUT请求?


46

假设REST API响应HTTP GET请求,在子对象中返回一些其他数据owner

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

显然,我们不希望任何人能够PUT支持

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

并取得成功。的确,在这种情况下,我们甚至可能不会采取任何措施来实现这一目标。

但是,这个问题不仅仅涉及子对象:通常,应该对不应在PUT请求中进行修改的数据执行哪些操作?

是否应要求PUT请求中缺少它?

是否应该默默丢弃?

是否应该检查它,如果它与该属性的旧值不同,则在响应中返回HTTP错误代码?

还是我们应该使用RFC 6902 JSON补丁而不是发送整个JSON?


2
所有这些都会起作用。我想这取决于您的要求。
罗伯特·哈维

我要说的是,最不惊奇的原则表示应该从PUT请求中删除它。如果不可能,请检查,如果不相同,则返回错误代码。静默丢弃是最糟糕的情况(用户发送的消息预计它将发生变化,并且您告诉他们“ 200 OK”)。
Maciej Piechotka 2013年

2
@MaciejPiechotka的问题在于,您不会像在插入或获取上那样使用相同的模型,我更希望使用相同的模型,并且简单地使用字段授权规则,因此如果它们输入一个值一个他们不应该更改的字段,他们返回403禁止访问,如果稍后设置了授权以允许它,则如果他们未被授权,他们将获得401 Unauthorized
Jimmy Hoffa 2013年

@JimmyHoffa:所谓模型,是指数据格式(因为可以选择使用MVC Rest框架来重用模型,如果使用的话-OP没有提及任何东西)?如果我不受框架的约束,并且会发现较早的错误/易于实现,然后检查更改,那么我会采用可发现性(然后,检查更改)(好吧,我不应该触摸XYZ字段)。无论如何,丢弃都是最糟糕的。
Maciej Piechotka

Answers:


46

无论是W3C规范还是REST的非正式规则,都没有规定说a PUT必须使用与其对应的相同模式/模型的规则GET

如果它们相似,那很好,但是PUT做些不同的事情并不少见。例如GET,为了方便起见,我看到了很多API,这些API在a返回的内容中包含某种ID 。但是PUT,使用ID时,该ID仅由URI确定,并且在内容中没有任何意义。正文中找到的任何ID都会被忽略。

REST和Web通常与“ 稳健性原则紧密相关:“对您的[发送]行为保持保守,对您接受的内容保持开放。” 如果您从哲学上同意这一点,那么解决方案就显而易见:忽略PUT请求中的任何无效数据。这适用于您的示例中的不可变数据和实际的废话,例如未知字段。

PATCH是另一种可能的选择,但是PATCH除非您实际要支持部分更新,否则您不应该实施。PATCH表示仅更新我包含在内容中的特定属性 ; 它意味着更换整个实体,但排除一些特定的领域。您实际上在谈论的并不是真正的部分更新,而是完全更新,幂等以及所有更新,只是资源的一部分是只读的。

如果选择此选项,则不错的选择是将响应中的实际更新的实体发送回200(确定),以便客户端可以清楚地看到只读字段未更新。

当然,有些人会以另一种方式思考-尝试更新资源的只读部分应该是一个错误。为此有一定道理,主要是基于以下事实:如果整个资源都是只读的,并且用户尝试更新它,则肯定会返回错误。它绝对违背了健壮性原则,但是对于API用户而言,您可能会认为它更具“自我记录性”。

有两个约定,这两个约定都与您的原始想法相对应,但我将在其上进行扩展。第一种是禁止只读字段出现在内容中,如果存在则返回HTTP 400(错误请求)。如果还有其他无法识别/无法使用的字段,则此类API也应返回HTTP 400。第二个是要求只读字段必须与当前内容相同,如果值不匹配,则返回409(冲突)。

我真的不喜欢使用409进行相等检查,因为它总是要求客户端执行一次操作GET才能检索当前数据,然后才能执行操作PUT。那不是很好,并且可能会导致某人某处的性能下降。我也真的不喜欢403(禁止访问),因为这意味着整个资源都受到保护,而不仅仅是一部分。因此,我的意见是,如果您绝对必须验证而不是遵循鲁棒性原则,请验证所有请求,并为具有额外或不可写字段的任何请求返回400。

确保您的400/409 /任何物品都包含有关特定问题以及如何解决的信息。

这两种方法都是有效的,但我希望前一种方法与鲁棒性原则保持一致。如果您曾经体验过使用大型REST API的经验,那么您将欣赏向后兼容性的价值。如果您决定删除现有字段或将其设置为只读,那么如果服务器仅忽略这些字段,并且旧客户端仍将运行,则这是向后兼容的更改。但是,如果您对内容进行严格的验证,则它不再向后兼容,并且旧客户端将停止工作。对于API的维护者及其客户,前者通常意味着较少的工作。


1
好的答案,并赞成。但是,我不确定我是否同意:“如果您决定删除现有字段或将其设置为只读,那么如果服务器仅忽略这些字段,并且旧客户端仍然可以工作,则这是向后兼容的更改。 ” 如果客户端依赖于此已删除/新只读字段,这是否仍会对应用程序的整体行为产生影响?在删除字段的情况下,我认为最好是显式生成错误而不是忽略数据。否则,客户端不知道其先前有效的更新现在失败了。
rinogo '16

这个答案是错误的。出于RFC2616的两个原因:1.(第9.1.2节)PUT必须独立。多次放置,将产生与只放置一次相同的结果。2.如果没有其他更改资源的请求,则获取资源应返回放置的实体
brunoais

1
如果仅在请求中发送了不可变值的情况下进行相等性检查,该怎么办?我认为这给了您两个世界中最好的;您不强制客户端执行GET,并且如果客户端发送的不可变值无效,您仍会通知他们出了点问题。
艾哈迈德·阿卜杜勒加尼

谢谢,根据经验,您在上几段中所做的深入比较正是我想要的。
dhill

9

同上效力

遵循RFC,PUT必须将完整的对象交付给资源。其主要原因是,PUT应该是幂等的。这意味着重复的请求应在服务器上评估为相同结果。

如果允许部分更新,则它不再是幂等的。如果您有两个客户。客户端A和客户端B,则可能会发生以下情况:

客户端A从资源图像中获取图片。其中包含对图像的描述,该描述仍然有效。客户端B放置新图像并相应地更新描述。图片已更改。客户A看到,他不必更改描述,因为它像他希望的那样,只放置了图像。

这将导致不一致,图像附加了错误的元数据!

更令人讨厌的是,任何中介都可以重复该请求。如果它以某种方式决定了PUT失败。

PUT的含义无法更改(尽管您可能会滥用它)。

其他选择

幸运的是,还有另一个选择,这就是PATCH。PATCH是一种允许您部分更新结构的方法。您可以简单地发送部分结构。对于简单的应用程序,这很好。这种方法不能保证是等效的。客户应以以下形式发送请求:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

服务器可以返回204(无内容)以标记成功。如果出错,则无法更新结构的一部分。PATCH方法是原子的。

此方法的缺点是,并非所有浏览器都支持此方法,但这是REST服务中最自然的选择。

补丁请求示例: http : //tools.ietf.org/html/rfc5789#section-2.1

Json修补

json选项似乎非常全面并且是一个有趣的选项。但是对于第三方来说可能很难实现。您必须确定用户群是否可以处理此问题。

这也有些令人费解,因为您需要构建一个小的解释器,该解释器将命令转换为部分结构,您将使用该部分结构来更新模型。该解释器还应检查所提供的命令是否有意义。有些命令会互相抵消。(写入fielda,删除fielda)。我认为您想将此报告给客户端,以限制其调试时间。

但是,如果您有时间,这是一个非常优雅的解决方案。您仍然应该验证字段。您可以将其与PATCH方法结合使用,以保留在REST模型中。但是我认为POST在这里可以接受。

变坏了

如果您决定使用PUT选项,则有些冒险。然后,您至少不应丢弃该错误。用户有一定的期望(数据将被更新),如果您破坏了期望,您将给一些开发人员带来不好的时光。

您可以选择进行标记:409冲突或403禁止访问。这取决于您如何看待更新过程。如果您将其视为一组规则(以系统为中心),那么冲突会更好。诸如此类,这些字段不可更新。(与规则冲突)。如果您将其视为授权问题(以用户为中心),则应返回“禁止”。使用:您无权更改这些字段。

您仍然应该强制用户发送所有可修改的字段。

强制执行此操作的合理选择是将其设置为仅提供可修改数据的子资源。

个人意见

就个人而言,我会去(如果您不必使用浏览器)简单的PATCH模型,然后再使用JSON补丁处理器对其进行扩展。这可以通过区分mimetypes来完成:json补丁的mime类型:

application / json-patch

和json:application / json-patch

使其易于分两个阶段实施。


3
您的幂等示例没有任何意义。您可以更改说明,也可以不更改。无论哪种方式,每次都会得到相同的结果。
罗伯特·哈维

1
您说得对,我想是时候上床睡觉了。我无法编辑。它是有关在PUT请求中发送所有数据的合理性的一个示例。感谢您的指导。
Edgar Klerks

我知道这是3年前的事,但是您知道在RFC的什么地方可以找到有关“ PUT必须将完整对象交付给资源”的更多信息。我在其他地方看到过此问题,但希望了解它在规范中的定义。
CSharper

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.