起因
长期以来,我基本上从事的都是移动端开发,对后台的API规范属于一知半解了,倒是以前研究过GET方法能不能有BODY,但从没想过HTTP的DELETE方法能不能有Body,毕竟我很少有用到DELETE,以至于我一直觉得DELETE支持BODY,直接到今天Calude给我上了一课,起因是我想写一个删除账号的API,要带上一个验证码,如果从RESTful规范和语义上说,用DELETE显然是更适合的,但Claude Code却建议我使用POST:
go
1. **`POST` 还是 `DELETE` 方法**:语义上"删除"更该用 HTTP `DELETE`,但 `DELETE` 带 body 在部分客户端/网关上支持不一致,而且现有 `signup`/`reset-password` 都是状态变更但用的 `POST`。为了跟现有风格一致、也避免 body-in-DELETE 的坑,**建议沿用 `POST`**。
也就是说DELETE方法在不同的地方对BODY的支持是不一致的,有的是支持的,有的是不支持。本着对真相的渴望,我又去问了Gemini,Gemini告诉我:"技术上可以,但极度不推荐 。根据 MDN Web Docs 的定义,HTTP 规范对DELETE 请求的 Body 没有定义明确的语义。这意味着标准允许请求带有 Body,但服务器在接收到带有 Body 的 DELETE 请求时,可以根据自身的实现自由处理(例如忽略它或直接处理)"。也就是说,你可以给DELTE加一个。而且大多数web服务器也不会明确禁止这样做。就像大多数web服务器也不会禁止GET请求带body一样。这更多的是服务端的endpoint handler和客户端使用服务的实现细节。
事实上真是这样的吗?
如果我们去查阅HTTP 协议的官方规范(RFC 9110),你会发现DELETE还真就不应该带BODY,规范原文如下:
markdown
A client **SHOULD NOT** generate content in a DELETE request unless it is made directly to an origin server that has previously indicated, in or out of band, that such a request has a purpose and will be adequately supported. An origin server SHOULD NOT rely on private agreements to receive content, since participants in HTTP communication are often unaware of intermediaries along the request chain.
翻译一下:
markdown
客户端**不应当**在 DELETE 请求中生成正文内容,除非该请求是直接发送给源服务器,且该服务器此前已通过带内或带外方式明确表明此类请求具有特定目的且会得到充分支持。源服务器**不应当**依赖私下协议来接收正文内容,因为 HTTP 通信的参与者通常无法察觉请求链中存在的中间件(如代理服务器等)。
SHOULD NOT本身不是禁止的意思,也就是说上面说的"技术上可以实现,但不推荐"系列。而规范中说到的豁免情况也就不难理解了,每个HTTP的具体实现都规范的遵循程序都不一致,比如说早期的Flutter DIO就支持DELETE带BODY,后来DIO为了遵循HTTP规范,又取消DELETE带BODY的支持,而早期JDK也是不支持的,CURL本身应该是支持,所以如果你能直接把请求直接发送给源服务器,且该服务器此前已通过带内(in-band)或带外(out-of-band)方式明确表明此类请求具有特定目的且会得到充分支持的情况下,你就可以使用带BODY的DELETE了。
其实从http报文的角度看,他们完全都是一样的。没有任何区别。大家能发送的信息都是一样的。你能做的我也能做,不一样的在于method所代表的这个请求的语义。
结论
说到底,在http规范任何方法都能发送请求实体。他们报文时,没有任何区别的;但是在浏览器中,比如说,XMLHttpRequest就遵循规范的限制,浏览器中ajax发送的http请求GET和DELETE请求不能携带实体。