DELETE 请求正文的 RESTful 替代方案

Posted

技术标签:

【中文标题】DELETE 请求正文的 RESTful 替代方案【英文标题】:RESTful Alternatives to DELETE Request Body 【发布时间】:2012-12-28 17:22:39 【问题描述】:

虽然HTTP 1.1 spec 似乎允许 DELETE 请求的消息正文,但它似乎表明服务器应该忽略它,因为它没有定义的语义。

4.3 消息正文

服务器应该在任何请求上读取并转发消息体;如果 请求方法不包括实体主体的定义语义, 那么在处理请求时应该忽略消息体。

我已经回顾了关于 SO 及其他主题的几个相关讨论,例如:

Is an entity body allowed for an HTTP DELETE request? Payloads of HTTP Request Methods HTTP GET with request body

大多数讨论似乎都同意在 DELETE 上提供消息正文可能是允许的,但通常不建议这样做。

此外,我注意到各种 HTTP 客户端库中的一种趋势,在这些库中似乎记录了越来越多的增强功能,以支持 DELETE 上的请求正文。大多数图书馆似乎都愿意,尽管偶尔会有一些最初的阻力。

我的用例要求在 DELETE 上添加一些必需的元数据(例如,删除的“原因”以及删除所需的一些其他元数据)。我考虑了以下选项,但似乎都不是完全合适且符合 HTTP 规范和/或 REST 最佳实践:

消息体 - 规范表明 DELETE 上的消息体没有语义价值; HTTP 客户端不完全支持;不是标准做法 Custom HTTP Headers - 要求自定义headers一般为against standard practices;使用它们与我的 API 的其余部分不一致,这些都不需要自定义标头;此外,没有好的 HTTP 响应可用于指示错误的自定义标头值(可能完全是一个单独的问题) 标准 HTTP 标头 - 没有合适的标准标头 查询参数 - 添加查询参数实际上改变了被删除的请求URI; against standard practices POST 方法 -(例如POST /resourceToDelete deletemetadata )POST 不是删除的语义选项; POST 实际上代表 相反 所需的操作(即 POST 创建资源下属;但我需要删除资源) 多种方法 - 将 DELETE 请求拆分为两个操作(例如,PUT 删除元数据,然后删除)将一个原子操作拆分为两个,可能会留下不一致的状态。删除原因(和其他相关元数据)不是资源表示本身的一部分。

我的首选可能是使用消息正文,其次是自定义 HTTP 标头;然而,如前所述,这些方法也有一些缺点。

是否有任何符合 REST/HTTP 标准的建议或最佳实践,以在 DELETE 请求中包含此类必需的元数据?还有其他我没有考虑过的替代方案吗?

【问题讨论】:

Jersey 等某些实现不允许 delete 请求的正文。 【参考方案1】:

尽管有一些建议不要将消息正文用于 DELETE 请求,但这种方法可能适用于某些用例。这是我们在评估问题/答案中提到的其他选项并与服务的消费者合作之后最终使用的方法。

虽然消息正文的使用并不理想,但其他选项都不是非常合适。请求正文 DELETE 使我们能够轻松清晰地围绕 DELETE 操作所需的附加数据/元数据添加语义。

我仍然愿意接受其他想法和讨论,但想结束这个问题的循环。感谢大家对这个话题的想法和讨论!

【讨论】:

这是个坏主意。这会给您带来麻烦的一个地方是,如果您后来决定使用 HTTP 加速服务,例如 Akamai EdgeConnect。我知道 EdgeConnect 从 HTTP DELETE 请求中剥离主体(因为它们消耗带宽可能是无效的)。类似的服务也很可能做同样的事情(请参阅 Kindle 的加速功能和其他类似 CDN 的服务)。您可能应该重新设计为您的服务不使用 HTTP 动词。大多数 API 使用 HTTP-verbs/classical-REST 几乎没有意义,而且 HTTP 动词传输问题很难解决。 我同意@Gabe,发送一个带有根据定义没有主体的方法的主体是在您的位穿过互联网管道时随机丢失数据的可靠方法,并且您将很难调试它。 这些反对使用 DELETE 的 cmets 是无关紧要的,直到它们解决了 OP 所具有的非常有效的问题。我正在尽我最大的努力坚持 REST 的精神,但是没有乐观并发的删除和没有原子批处理操作的删除在现实生活中是不切实际的。这是 REST 模式的一个严重缺陷。 我支持@Quarkly。我不明白 RESTFUL 的想法是关于我们应该如何检查并发等。并发检查不属于客户端。 ***.com/a/60357432/6476436 引用lists.w3.org/Archives/Public/ietf-http-wg/2020JanMar/0123.html 在其中 Roy Fielding 说“一个机构不能改变收到的请求的含义。绝对禁止它们对请求的处理或解释产生任何影响” .在您的情况下,身体会影响处理。我有一个类似的问题,我的“删除原因”可以是任意长的 Unicode 文本。我也可以使用 DELETE——因为我不能用 DELETE 删除某些东西太疯狂了——但我认为 POST 更符合规范。【参考方案2】:

您似乎想要的是两件事之一,两者都不是纯粹的DELETE

    您有两个操作,删除原因的PUT 后跟资源的DELETE。删除后,任何人都无法再访问该资源的内容。 “原因”不能包含指向已删除资源的超链接。或者, 您正在尝试使用 DELETE 方法将资源state=active 更改为 state=deleted。 state=deleted 的资源会被您的主 API 忽略,但管理员或具有数据库访问权限的人可能仍然可以读取。这是允许的 - DELETE 不必删除资源的支持数据,只需删除在该 URI 处公开的资源。

DELETE 请求上需要消息正文的任何​​操作都可以分解为最一般的情况,POST 用于对消息正文执行所有必要的任务,以及DELETE。我认为没有理由破坏 HTTP 的语义。

【讨论】:

如果PUT 原因成功而DELETE 资源失败会怎样?如何防止不一致的状态? @Lightman PUT 仅指定意图。它可以在没有相应的 DELETE 的情况下存在,这表明有人想删除但它失败了,或者他们改变了主意。颠倒调用的顺序也将允许 DELETE 无理由地发生 - 提供理由将被视为仅作为注释。出于这两个原因,我建议使用上面的选项 2,即更改底层记录的状态,例如导致 HTTP 资源从其当前 URL 中消失。然后垃圾收集器/管理员可以清除记录【参考方案3】:

鉴于您的情况,我会采取以下方法之一:

发送 PUT 或 PATCH:我推断删除操作是虚拟的,本质上需要删除原因。因此,我相信通过 PUT/PATCH 操作更新记录是一种有效的方法,即使它本身不是 DELETE 操作。 使用查询参数:资源 uri 没有被更改。我实际上认为这也是一种有效的方法。您链接的问题是在缺少查询参数时不允许删除。在您的情况下,如果原因未在查询字符串中指定,我将只有一个默认原因。该资源仍将是resource/:id。您可以使用资源上的链接标头为每个原因使其可被发现(每个原因都带有 rel 标记以识别原因)。 根据原因使用单独的端点:使用像 resource/:id/canceled 这样的 URL。这确实改变了 Request-URI 并且绝对不是 RESTful。同样,链接标头可以使这一点变得可发现。

请记住,REST 不是法律或教条。更多地将其视为指导。因此,当不遵循针对您的问题域的指导有意义时,不要这样做。只需确保您的 API 使用者了解差异即可。

【讨论】:

关于查询参数的使用,我的理解是查询是每个 section 3.2 的请求 URI 的一部分,因此使用这种方法(或类似地,单独的端点)违背了定义DELETE 方法,这样“Request-URI 标识的资源”就被删除了。 资源由uri路径标识。所以对/orders/:id 的GET 将返回与/orders/:id?exclude=orderdetails 相同的资源。查询字符串仅向服务器提供提示 - 在这种情况下,排除响应中的订单详细信息(如果支持)。同样,如果您将 DELETE 发送到 /orders/:id/orders/:id?reason=canceled/orders/:id?reason=bad_credit,您仍然在对同一底层资源进行操作。为了保持“统一接口”,我有一个默认原因,因此不需要发送查询参数。 @shelley 您对查询字符串的担忧是正确的。查询字符串是 URI 的一部分。向 /foo?123 发送 DELETE 请求意味着您正在删除与向 /foo?456 发送 DELETE 不同的资源。 @codeprogression 抱歉,您说的大部分内容都是错误的。资源由整个 URI 标识,而不仅仅是路径。不同的查询字符串是不同的资源(在 HTTP 意义上的“资源”一词)。此外,统一接口不需要默认原因。该术语指的是使用 GET、PUT、POST、PATCH 和 DELETE,它们是 HTTP 定义的。共同点是在供应商之间(用户代理供应商、API 供应商、缓存代理供应商、ISP 等),而不是在自己的 API 中(尽管为了用户的理智,设计也应该是统一的!)。 @Nicholas 我不明白你需要争论三年前结束的讨论。从以 REST 为中心的角度来看,我所做的答案和 cmets 是有效且正确的。 REST 不是 HTTP(也不是 HTTP 的任何供应商实现)。在 REST 的上下文中,资源是相同的。正如我在回答中所说,REST 不是法律或教条,而是指导。【参考方案4】:

我建议您将所需的元数据作为 URI 层次结构本身的一部分。一个例子(朴素):

如果您需要根据日期范围删除条目,而不是在正文中或作为查询参数传递开始日期和结束日期,请构建 URI,以便将所需信息作为 URI 的一部分传递。

例如

DELETE /entries/range/01012012/31122012 -- 删除 2012 年 1 月 1 日至 2012 年 12 月 31 日之间的所有条目

希望这会有所帮助。

【讨论】:

不包括发送删除原因(即评论字段)等情况。 哇。那是个可怕的主意。如果元数据过多,会导致 URI 的大小限制膨胀。 此方法不遵循 RESTful 实践,因此不推荐使用,因为您将拥有一个复杂的 URI 结构。端点被相互交织的资源识别与元数据混淆,随着 API 的变化,最终将成为维护的噩梦。在查询参数或有效负载中指定range 更可取,这是这个问题的核心:了解解决问题的最佳实践方法,我想说的不是这个。 @digitaldreamer - 我不明白你所说的复杂的 URI 结构是什么意思?此外,这是一个 HTTP DELETE,因此有效负载不是一个选项,而是查询参数是的。

以上是关于DELETE 请求正文的 RESTful 替代方案的主要内容,如果未能解决你的问题,请参考以下文章

Ajax中Put和Delete请求传递参数无效的解决方法(Restful风格)

让python bottle框架支持jquery ajax的RESTful风格的PUT和DELETE等请求

beego框架(golang)学习过滤器(实现restful请求)

springboot的服务端Restful风格 API接口,在不同场景下,设置不同的请求及传参方式的设计,及其他异常场景解决方案

RESTful API:在请求正文中而不是在 URI 中传递父资源定位器是不是更实用?

如何在 Flutter 中使用 JSON 正文发出 http DELETE 请求?