HTTP 很少会新增一个方法。
对于大多数开发者来说,整个职业生涯里翻来覆去用的也就是那几个再熟悉不过的动词:
GET,用来获取数据。
POST,用来创建或提交数据。
PUT,用来替换资源。
DELETE,用来删除资源。
这些规则我们背得很熟。但在它们中间,其实一直存在着一个尴尬的空洞。
问题是:当你想进行一个带条件、筛选、排序、嵌套逻辑,甚至包含数组的复杂查询时,到底该用什么?
我说的不是那种“给我用户 ID 为 42 的信息”这种简单查询。那种情况用 GET 自然是干净利落的。我说的是另一种情况:给我所有角色是 admin、部门是 engineering 的用户,还要按姓名排序、分页返回,并限定日期范围。
这已经不是单纯地查一个资源了。这是一个复杂的读操作,而 HTTP 协议这么多年来,从来没给它一个真正合适的位置。
为什么 GET 本来就不适合做这件事?
很多人的第一反应是用 GET。毕竟你只是要读取数据,并没有修改任何东西,这个直觉本身没错。GET 是 safe 的,也是 idempotent 的。这意味着浏览器、缓存、代理服务器以及请求链路上的所有中间层,都明白一个道理:这个请求调用一次和调用两次,不应该产生不同的结果。它不会修改服务器状态,所以响应可以被安全地缓存。
但问题出在另一个层面:GET 把数据放在 URL 里。
比如,你要查 role 是 admin、department 是 engineering 的用户,请求可能长这样:
/users?role=admin&department=engineering
两个筛选条件,这看起来甚至很优雅。但如果你继续加条件呢?加入日期范围、排序字段、一个嵌套的“OR this AND that”逻辑,再加一个 ID 数组,很快你的 URL 就会变成一串难以阅读的编码字符。
再往前推一步,你还会撞上一个硬限制:浏览器和服务器对 URL 长度都有限制。一旦查询条件太复杂,请求可能还没进入业务逻辑,就直接触发了 414 URI Too Long 错误。
于是,有人开始想:那把参数放到 body 里总行了吧?这个想法听起来很合理,技术上,GET 请求在传输层确实可以带 body。但在现实世界中,这件事非常混乱。有些客户端会把 body 剥离,有些代理会直接丢弃它,还有些防火墙会阻断这类请求。不同的库、服务器、网关对其行为的处理都可能不同。
根本原因不是“有人忘了给 GET 加上 body 功能”,而是 HTTP 协议从未有过一个被广泛接受、明确标准化的 GET with body 的语义。因此,它是不可靠的。它不是一个隐藏特性,更像是一片“未定义行为”的沼泽。你可能碰巧用对了,但这并不意味着你应该依赖它。
那个一直在“撒谎”的临时方案
于是,开发者们做了当时唯一能做的事:用 POST 来做搜索。
从工程角度看,这确实能跑通。请求可以发出去,复杂的筛选条件也能安全地放在 body 里,复杂的 JSON 不用再费力塞进 URL。一切看起来都解决了。
但根本问题在于,POST 生来就不是为这事儿设计的。POST 的语义是改变服务器状态——创建记录、提交表单、触发副作用。换句话说,它既不是 safe,也不是天然 idempotent 的。
这会直接影响整个 HTTP 基础设施如何看待这个请求。缓存、CDN、浏览器、中间代理,都会把 POST 当作一个可能修改状态的请求。即使你的 POST /search 接口根本没有触碰数据库里的任何一行数据,浏览器不知道,代理不知道,CDN 也不知道。所以,它不会像 GET 那样自然地缓存响应,连接中断后也不能放心地重试。
结果就是,你得到了一个非常别扭的接口:它明明是在读数据,却穿着一件“我要修改数据”的外衣。方法本身在撒谎,而整个链路中的每一个组件,都会把这个谎言当真。
QUERY 来了
这个缺口,现在终于被补上了。2026 年 6 月 15 日,IETF 发布了 RFC 10008,正式定义了一个全新的 HTTP 方法:QUERY。
这是自 2010 年 PATCH 方法进入标准轨道以来,首个进入标准轨道的全新 HTTP 方法。QUERY 做的事情非常直接:它继承了 POST 能携带 body 的能力,同时又保留了 GET 那套 safe 和 idempotent 的语义。
一个 QUERY 请求可以长这样:
QUERY /users HTTP/1.1
Host: example.com
Content-Type: application/json
{
"role": "admin",
"department": "engineering",
"sort": "name",
"page": 1
}
你再也不用把复杂的查询硬塞进 query string,也不用把嵌套对象编码成一堆 URL 乱码,更不用担心 414 错误了。筛选条件就像在 POST 里一样,可以老老实实地躺在 body 里。
但它又和 POST 不同。QUERY 会明确地告诉路径上所有的代理、缓存和重试机制:这是一个安全的读取操作,可以重复执行,重复执行也不会产生额外的副作用。
RFC 还新增了一个响应头:Accept-Query。服务器可以通过它声明某个 endpoint 支持 QUERY 方法,以及它愿意接受哪些 body 格式。
有一点必须澄清:QUERY 并不是让 GET 终于可以带 body 了。GET 还是原来的 GET,没有任何变化。QUERY 是一个独立的新方法,它与 GET 和 POST 并驾齐驱,而非替代或扩展它们。如果你看到有人把这次更新解释成“现在 GET 请求可以有 body 了”,那是个很常见但并不准确的说法。QUERY 出现的根本原因,恰恰是因为 GET with body 从来都不可靠。所以,工作组没有去硬改一个语义已经非常明确的老方法,而是选择新增一个动词。
缓存终于有救了
这可能是 QUERY 最精妙的设计。它的响应是可以缓存的,但其缓存键不能像 GET 那样只看 URL,因为真正的查询条件在 body 里。
RFC 10008 的要求是:缓存键必须由请求的 body 和其元数据(包括 Content-Type)共同构成。这意味着,反向代理或 CDN 可以读取 body 字节和相关头部,当下一次进来一个完全一致的查询时,就能直接返回缓存的响应。
这才是复杂读取操作真正需要的缓存模型。以前那些基于 POST 的搜索接口,一直很难标准化地实现这一点。现在,QUERY 为它提供了一个名正言顺的位置。
现在谁已经开始支持了?
RFC 10008 的作者包括 J. Reschke、来自 Cloudflare 的 J.M. Snell 以及来自 Akamai 的 M. Bishop。Cloudflare 和 Akamai 这两家顶级 CDN 公司共同参与这个规范,本身就是一个强烈的信号:CDN 和代理层面的支持,可能会比很多应用框架来得更早。
目前的支持情况大致如下:
- Go 和 Rust:已经允许发送自定义 method string,所以现在就可以调用
QUERY,无需等待库的更新。例如在 Rust 的 reqwest 库中,可以直接使用 Method::from_bytes(b"QUERY")。
- .NET:已实现双向支持。客户端有
HttpMethod.Query,服务端有 HttpMethods.Query 和 HttpMethods.IsQuery,从 .NET 10 开始可用。
- Node.js:从
21.7.2 和 22+ 版本开始原生解析 QUERY,Fastify 等框架也可以通过 addHttpMethod 来支持它。
- Spring Framework:已经有一个开放的 Pull Request,正在等待维护者审查。
- Nginx 和 Apache:这类反向代理需要进行显式配置,才能正确识别并处理
QUERY 方法。
- 浏览器:技术上现在就能发送
QUERY。因为 fetch() API 将 HTTP method 当作普通字符串处理,所以 fetch(url, { method: "QUERY" }) 在现有浏览器中是可行的。不过,MDN 目前还未将其文档化,且 QUERY 不在 CORS 安全清单方法之列。这意味着跨域 QUERY 请求每次都会触发一次 preflight OPTIONS 请求,你的服务器必须明确返回 Access-Control-Allow-Methods: QUERY 头,否则请求会失败。
QUERY 方法目前处于 “Proposed Standard”(推荐标准)阶段。这意味着它已不再停留于纯理论文本,而是进入了真实实现开始涌现的阶段。但要让浏览器、服务器、代理、CDN 和框架全面铺开,仍需要时间。现实一点看,真正大范围普及可能要等到 2027 至 2028 年。所以,请不要明天就把所有 POST /search 接口都删掉。现在更合理的做法是,开始理解它、实验它,并为未来的迁移留好位置。
你应该认真思考的两个问题
如果你是那种“代码能跑就行”,但不太关心它为什么能跑的开发者,有两个问题值得你停下来想一想。
第一,你真的理解 idempotent 的含义吗?你知道为什么这个属性能让 GET 被安全地缓存和重试,而 POST 却不能吗?
第二,在这个 RFC 发布之前,你是否已经意识到:GET 方法本来就不该依赖请求 body。那些你在某些客户端库里见过的“给 GET 塞 body”的技巧,一直都是未定义行为,而不是一个官方的隐藏能力?
这两个问题,在面试里经常出现,而它们也正是 QUERY 被设计出来所要解决的核心矛盾。
说到底,QUERY 的出现,不是为了让 HTTP 多一个新玩具。它是在修正一个我们用了很多年、但并不诚实的接口。复杂搜索,终于不用再假装自己是一个 POST 请求了。
如果你想深入了解HTTP、HTTPS、DNS等网络协议栈的底层原理与实战,可以在云栈社区找到更多技术同好的深度讨论与经验分享。