在 URL 中表示资源克隆操作的 restful 方式是什么?

我有一个 RESTAPI,它公开了一个复杂的大型资源,我希望能够克隆这个资源。假设资源在 /resources/{resoureId}处公开

为了克隆10号资源,我可以这样做。

  • GET /resources/10
  • Put 的 POST /resources/主体包含 GET /resources/10表示的副本,但没有 id,因此 POST创建一个新资源。

这种方法的问题在于,资源非常庞大和复杂,将完整的表示返回给客户机,然后让客户机将其发送回来,这样做毫无意义,因为这将完全浪费带宽和服务器上的 CPU。克隆服务器上的资源要容易得多,所以我想这样做。

我可以执行类似于 POST /resources/10/clonePOST resources/clone/10的操作,但是这两种方法都感觉不对,因为 URL 中的动词。

在这种情况下,构建 url 的最“静止/nouny”的方法是什么?

23322 次浏览

由于 HTTP 中没有复制或克隆方法,所以您想做什么实际上取决于您自己。在这种情况下,POST似乎是完全合理的,但其他标准采取了不同的方法:

  • WebDAV 添加了一个 COPY
  • Amazon S3使用 PUT,没有主体,只有一个特殊的 x-amz-copy-source头,他们称之为 PUT Object - Copy

这两种方法都假设您知道目标 URI。您的示例似乎缺少一个已知的目的地 uri,因此 必须的基本上使用 POST。不能使用 PUT 或 COPY,因为创建操作不是幂等的。

如果您的服务将 POST /resources定义为“创建新资源”,那么为什么不简单地定义另一种方法来指定资源而不是 POST 的主体呢?例如,POST /resources?source=/resources/10的身体是空的。

如果这对任何人都有帮助的话,威尔就直接说出来吧。
我们有一个类似的场景,我们提供“克隆 vm”作为扩展 IaaS 产品的特性。因此,如果用户希望向外扩展,那么他们必须点击 POST: /vms/vm101端点,request _ body 为

{"action": "clone", // Specifies action to take, since our users can do couple of other actions on a vm, like power_off/power_on etc.
"body": {"name": [vm102, vm103, vm104] // Number of clones to make
"storage": 50, ... // Optional parameters for specifying differences in specs one would want from the base virtual machine
}

以及 vm101即 vm102、 vm103和 vm104的3个克隆。

弗朗西斯的回答很棒也许正是你想要的。也就是说,它在技术上不是 RESTful 的,因为(正如他在评论中所说的)它确实依赖于客户端提供的带外信息。因为问题是“什么是静止的方式”而不是“什么是好的方式/最好的方式”,这让我思考 是否有一个静止的解决方案。我认为接下来的是一个 RESTful 解决方案,尽管我不确定它在实践中是否一定更好。

首先,正如您已经指出的那样,GET 后跟 POST 是一种简单而明显的 RESTful 方式,但它并不高效。因此,我们正在寻找一个优化,我们不应该感到太惊讶,如果它觉得有点不自然的解决方案!

POST + sourceId 解决方案创建了一个特殊的 URL ——它不指向资源,而是指向执行某些操作的指令。每当您发现自己正在创建这样的特殊 URL 时,都值得考虑一下是否可以通过定义更多的资源来解决这个问题。

我们想要复制的能力

resources/10

如果我们找到另一种资源呢:

resources/10/copies

... 这个资源的定义是简单的“资源的集合是资源/10的副本”。

定义了这个资源之后,我们现在可以用不同的术语重新声明我们的拷贝操作——而不是说“我想要服务器拷贝资源/10”,我们可以说“我想要添加一个新的东西到资源/10的副本的集合中”。

这听起来很奇怪,但是它很自然地适合 REST 语义。例如,假设这个资源目前看起来是这样的(我在这里将使用一个 JSON 表示) :

[]

我们可以用一个 POST 或 PATCH [1]来更新它:

POST resources/copies/10
["resources/11"]

注意,我们发送到服务器的所有内容都是关于集合的元数据,因此它非常高效。我们可以假设服务器现在知道从哪里获取要复制的数据,因为这是此资源定义的一部分。我们还可以假设客户机知道这会导致出于同样的原因在“ resources/11”创建一个新资源。

有了这个解决方案,所有内容都被清楚地定义为一个资源,所有内容都有一个规范的 URL,客户机永远不需要带外信息。

最后,仅仅为了让自己更加安静,是否值得使用这种奇怪的感觉解决方案呢?这可能取决于您的个人项目。但是尝试通过创建不同的资源来以不同的方式构建问题总是很有趣的!

[1]我不知道在“ resources/10/copy”上允许 GET 是否有意义。显然,一旦原始资源或其副本发生变化,该副本就不再是副本,不应该出现在这个集合中。在实现方面,我认为没有必要让服务器负担跟踪它的工作,所以我认为应该将它作为仅更新的资源来处理。

我认为 POST /resources/{id}是复制资源的一个很好的解决方案。

为什么?

  • POST /resources是 CREATE 新资源的默认 REST-Standard
  • POST /resources/{id}在大多数 REST apis 中不可能存在,因为这个 id 已经存在了——您(客户机)定义这个 id 时将永远不会生成新的资源。服务器将定义 id。

还要注意,您永远不会将资源 A 复制到资源 B 上。因此,如果你想复制 id = 10的现有资源,有些答案建议这样做:

POST /resources?sourceId=10


POST /resources?copyid=10

但这更简单:

POST /resources/10

它创建了一个10的副本-你必须从存储中获取10,所以如果你没有找到它,你不能复制它 = 抛出一个404未找到。

如果它确实存在,则创建一个副本。

因此,使用这个思想,你可以看到它没有意义做以下,复制一些 b 资源到一些资源:

POST /resources?source=/resources/10
POST /resources-a?source=/resources-b/10

那么为什么不简单地使用 POST/resources/{ id }

  • 它将创建一个新资源
  • 复制父类由 {id}定义
  • 该副本将仅位于同一资源上
  • 它是最像 REST 的变体

你觉得这个怎么样?

您希望创建特定资源的副本。在这种情况下,我的方法是使用以下端点: POST /resources/{id}/copy,读取它“创建资源{ id }的副本”