以Restful方式删除一批项目

118
REST的维基百科文章 中指出,如果您使用 http://example.com/resources DELETE 方法,那意味着您正在删除整个集合。
如果您使用 http://example.com/resources/7HOU57Y DELETE 方法,那意味着您正在删除该元素。
我正在做一个网站,注意不是网络服务。
我有一个列表,其中每个项目都有一个复选框。一旦我选择多个项目进行删除,我将允许用户按一个名为DELETE SELECTION的按钮。如果用户按下该按钮,将弹出一个js对话框询问用户确认删除操作。如果用户确认,所有项目都将被删除。
那么,我应该如何以符合RESTful标准的方式删除多个项目?
请注意,目前在网页中使用DELETE时,我使用FORM标签作为POST操作,但包括一个值为DELETE的_method,因为这是其他人在SO上指示如何为网页执行RESTful删除的方法。

1
这些删除操作是否必须具备原子性?如果第 31 个项目无法被删除,你真的想要撤消前 30 个项目的删除操作吗? - Darrel Miller
@darrelmiller 很好的问题。我认为如果删除是原子性的,那么它将不太有效率。因此,我倾向于使用DELETE FROM tablename WHERE ID IN ({list of ids})。如果有人能指出这是否是一个好主意或者纠正我,那将不胜感激。另外,如果第21个项目被删除,我不需要前20个项目的反向操作。同样地,如果有人能向我展示需要反转和不需要反转的方法的区别,我将不胜感激。 - Kim Stacks
1
注意:在“IN”子句中可能存在限制;例如,在Oracle中,您最多可以放置1000个ID。 - rob
Google的API设计指南提供了在REST API中创建自定义(批量)操作的解决方案,可以在这里查看我的答案:https://dev59.com/cmEh5IYBdhLWcg3w7XPt#53264372 - Felix K.
8个回答

60

一种选择是创建一个删除"事务"。因此,您可以POST到类似于http://example.com/resources/deletes的新资源,其中包含要删除的资源列表。然后在应用程序中,您只需执行删除操作。当您进行POST时,应返回创建的事务位置,例如http://example.com/resources/deletes/DF4XY7。对其进行GET可能会返回事务的状态(已完成或正在进行)和/或要删除的资源列表。


2
与您的数据库无关。通过“事务”,我只是指要执行的操作列表。在这种情况下,它是一个删除列表。您需要在应用程序中创建一个新的列表(删除列表)作为资源。您的Web应用程序可以按照您想要的方式处理该列表。该资源具有URI,例如http://example.com/resources/deletes/DF4XY7。这意味着您可以通过GET请求该URI来检查删除的状态。如果您需要从Amazon S3或其他CDN中删除图像,并且该操作可能需要很长时间才能完成,那么这将非常方便。 - rojoca
2
+1 这是一个不错的解决方案。@rojoca 提出创建一种新类型的资源实例,其唯一任务是删除一组资源,而不是向每个资源发送 DELETE 请求。例如,您有一个用户资源集合,并且想要从集合中删除用户 Bob、Dave 和 Amy,因此您可以创建一个新的 Deletion 资源,将 Bob、Dave 和 Amy 作为创建参数 POST。Deletion 资源被创建,代表着异步地从 Users 集合中删除 Bob、Dave 和 Amy 的过程。 - Mike Tunnicliffe
1
我认为DF4XY7是一个生成的唯一标识符,也许直接使用保存到数据库时生成的ID更自然,例如example.com/resources/deletes/7。我的想法是创建Deletion模型并将其保存在数据库中,您可以让异步进程删除其他记录并更新Deletion模型以显示完成状态和任何相关错误。 - Mike Tunnicliffe
1
@rojoca,我不明白为什么你要采用两步方法,而不是直接向http://example.com/resources/selections/这样的URI发送一个DELETE请求,在请求的负载(正文)中发送您希望删除哪些项目的数据。据我所知,没有任何阻止您这样做的东西,但我总是会遇到“但它不符合RESTful”的反驳。 - thecoshman
2
@rojoca是的,我认为问题在于HTTP非常适用于“DELETE用于删除单个资源”。无论你做什么,多重删除都有点像黑客。您仍然可以向客户端返回一个“作业”,告诉他们正在处理此任务(可能需要一些时间),但请使用此URI检查进度。我阅读了规范,并认为DELETE可以像其他请求一样具有正文。 - thecoshman
显示剩余8条评论

58
我认为至今为止,rojoca的回答是最好的。一种微小的变化可能是,在同一页上取消javascript确认,而是创建选择并重定向到它,在该页面上显示确认消息。换句话说:
从:
http://example.com/resources/ 做一个POST请求,并将ID的选择发送到:
http://example.com/resources/selections 如果成功,应该以HTTP/1.1 201 created响应,并在Location标头中添加链接:
http://example.com/resources/selections/DF4XY7 然后在这个页面上,你会看到一个(javascript)确认框,如果你确认,它会发出以下请求:
DELETE http://example.com/resources/selections/DF4XY7 如果成功,应该以HTTP/1.1 200 Ok的方式响应(或者适用于成功删除的其他内容)。

我喜欢这个想法,因为你不需要任何重定向。通过整合AJAX,你可以在不离开页面的情况下完成所有操作。 - rojoca
执行完这个DELETE http://example.com/resources/selections/DF4XY7操作后,我会被重定向回example.com/resources吗? - Kim Stacks
9
这种两步式的多项删除方法似乎是一个常见的建议,但为什么呢?为什么不直接向URI http://example.com/resources/selections/ 发送一个DELETE请求,在请求的负载(主体)中发送要删除的数据。据我所知,没有任何阻止你这样做的东西,但每次我都会遇到“但它不是RESTful”的回应。 - thecoshman
9
DELETE命令可能被HTTP基础设施忽略请求体:https://dev59.com/UHVC5IYBdhLWcg3wbQfA - Luke Puplett
1
DELETE 可以有一个主体,但是默认情况下,它的许多实现禁止使用主体。 - dmitryvim
如果我们真的想要实现“RESTful”,那么DELETE http://example.com/resources/selections/DF4XY7应该删除具有ID=DF4XY7的已创建的selection资源;而不是这个选择所引用的对象。也许更好的方法是使用POST http://example.com/resources/selections/DF4XY7/deletion在此“selection”资源的顶部创建一个新的“deletion”子资源。尽管如此,它看起来仍然很丑陋。 :) - Ruslan Stelmachenko

48
这是亚马逊对其S3 REST API所做的事情。
单个删除请求:
DELETE /ObjectName HTTP/1.1
Host: BucketName.s3.amazonaws.com
Date: date
Content-Length: length
Authorization: authorization string (see Authenticating Requests (AWS Signature Version 4))

多对象删除请求:

POST /?delete HTTP/1.1
Host: bucketname.s3.amazonaws.com
Authorization: authorization string
Content-Length: Size
Content-MD5: MD5

<?xml version="1.0" encoding="UTF-8"?>
<Delete>
    <Quiet>true</Quiet>
    <Object>
         <Key>Key</Key>
         <VersionId>VersionId</VersionId>
    </Object>
    <Object>
         <Key>Key</Key>
    </Object>
    ...
</Delete>           

但是Facebook Graph API, Parse Server REST APIGoogle Drive REST API更进一步,使您能够在一个请求中“批处理”单个操作。

下面是来自Parse Server的一个示例。

单个删除请求:

curl -X DELETE \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  https://api.parse.com/1/classes/GameScore/Ed1nuqPvcm

批量请求:
curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "requests": [
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1337,
              "playerName": "Sean Plott"
            }
          },
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1338,
              "playerName": "ZeroCool"
            }
          }
        ]
      }' \
  https://api.parse.com/1/batch

14

有趣的是,我认为同样的方法也适用于对多个实体进行PATCH操作,需要考虑我们在URL、参数和REST方法中的含义。

  1. 返回所有'foo'元素:

    [GET] api/foo

  2. 返回带有特定id过滤器的'foo'元素:

    [GET] api/foo?ids=3,5,9

其中URL和筛选器确定"我们正在处理哪些元素?",而REST方法(在本例中为"GET")表示"如何处理这些元素?"

  1. 因此,使用数据foo [read] = 1对多个记录进行PATCH标记为已读

    [PATCH] api/foo?ids=3,5,9

  1. 最后,要删除多个记录,此端点最合理:

    [DELETE] api/foo?ids=3,5,9

请理解我认为这没有任何"规则" - 对我来说它只是"有道理的"


实际上关于 PATCH: 如果您将实体列表视为一个实体本身(即使是数组类型),则它的意思是部分更新。发送部分数组(仅包含要更新的ID)或部分实体,然后可以省略查询字符串,从而不需要表示多个实体的URL。 - Szabolcs Páll
确实,这是一个简单的解决方案。为什么有些人要坚持让事情变得更加困难,有时候我真的无法理解。 - html_programmer

13

我会建议使用DELETE http://example.com/resources/id1,id2,id3,id4 或 DELETE http://example.com/resources/id1+id2+id3+id4。正如这篇维基百科文章引用的那样,"REST是一种架构(...)而不是协议",因此我认为没有一种单一的方法来完成这个任务。

我知道上述方法在HTML中无法实现,需要JS来实现,但我感觉REST是:

  • 创建时没有考虑到诸如事务之类的细节。谁需要操作多个项目?这在HTTP协议中被证明是可以接受的,因为它不打算通过它提供除静态网页之外的任何内容。
  • 在当前模型中不必要地调整得不够好-即使是纯HTML的模型也是如此。

谢谢 - 如果您想要删除整个集合,那么ID是否应该被省略? - BenKoshy
我有一种感觉,REST 的创建似乎没有考虑到像事务这样的细节问题。但我认为这并不完全正确。如果我理解正确,在 REST 中,事务是由资源而不是方法表示的。在这篇博客文章中有一些很好的讨论,最终得出了这个评论:https://jcalcote.wordpress.com/2009/08/06/restful-transactions/#comment-359。 - Paul D. Waite

3
Decent Dabbler回答rojocas的回答所说,最常规的方法是使用虚拟资源来删除一组资源,但我认为这种方法从REST的角度来看是不正确的,因为执行DELETE http://example.com/resources/selections/DF4XY7 应该删除选择资源本身,而不是选定的资源。
根据Maciej Piechotkafezfox的回答,我有一个异议:有更常规的传递ids数组的方法,那就是使用数组操作符: DELETE /api/resources?ids[]=1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d&ids[]=7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b 这样,您就可以攻击Delete Collection端点,并以正确的方式使用查询字符串进行过滤。

-1

我曾经遇到过删除多个项目的情况。最终我采用了DELETE操作,要删除的项目ID是HTTP头部的一部分。


-2

由于没有“正确”的方法来做到这一点,我过去所做的是:

使用xml或json编码数据将DELETE发送到http://example.com/something

当您收到请求时,请检查是否为DELETE,如果是,则读取要删除的内容。


这是我认为有意义的方法,你只需在一个请求中发送数据,但我总是遇到“但它不是RESTful”的问题。你有没有任何来源表明这是一种可行且“RESTful”的方法? - thecoshman
13
这种方法的问题在于,DELETE 操作不会期望请求体(body),因此互联网上的一些中间路由可能会在您没有控制或知情的情况下将其删除。因此,在 DELETE 请求中使用请求体(body)是不安全的! - Alex White
Alex的评论参考链接:https://dev59.com/UHVC5IYBdhLWcg3wbQfA - Luke Puplett
1
DELETE请求消息中的有效载荷没有定义的语义;在DELETE请求中发送有效载荷主体可能会导致某些现有实现拒绝该请求。 - cottton

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