HTTP QUERY:终于有了能带 Body 的安全查询方法

2026-06-30 33 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

IETF 发布 RFC 10008,正式给 HTTP 增加了一个新方法:QUERY。它瞄准的是一个长期尴尬的问题:很多查询操作本质上像 GET 一样安全、幂等,不应该修改服务器状态,但查询条件又复杂到不适合塞进 URL。过去大家只能在“超长 GET”和“语义不准的 POST”之间二选一,现在多了一个更贴切的选择。

它不是“带 Body 的 GET”,但确实解决了 GET 的痛点

GET 的优势很清楚:语义简单、安全、可缓存、容易被代理和浏览器理解。但它的查询参数通常放在 URL 里,现实中会遇到几个问题:

  • URL 长度限制:浏览器、网关、代理、日志系统都可能有自己的上限。
  • 结构表达别扭:复杂过滤条件、嵌套对象、数组组合用 query string 会很难读。
  • 敏感信息外泄:URL 常被日志、Referer、监控系统记录,复杂查询条件不一定适合暴露在那里。
  • 语义折中:很多团队用 POST /search 表达查询,但 POST 默认不是安全方法,缓存和中间件也不会自然按查询请求处理。

QUERY 的定位是:像 GET 一样用于安全、幂等的检索,但允许请求体承载查询表达式。它不是让所有 GET 都迁移过去,而是给“复杂查询”一个更准确的 HTTP 语义。

语义重点:安全、幂等、可表达复杂查询

理解 QUERY 时,可以抓住三个关键词。

安全:客户端发送 QUERY 不应该触发业务状态变化。比如搜索商品、筛选日志、查询报表都适合;下单、扣库存、提交审批不适合。

幂等:同一个 QUERY 请求执行一次或多次,业务结果不应因为执行次数而改变。当然,底层数据可能随时间变化,这和 GET 一样,不等于响应永远相同。

请求体:复杂查询条件可以放进 body,例如 JSON、SQL-like DSL、Elasticsearch 风格查询、GraphQL-like 查询对象等。这样既避免 URL 过长,也让 API 描述更清晰。

一个典型请求可以长这样:

QUERY /orders/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json

{
  "status": ["paid", "shipped"],
  "created_after": "2025-01-01T00:00:00Z",
  "customer": {
    "country": "CN",
    "vip": true
  },
  "sort": [{ "field": "created_at", "direction": "desc" }],
  "limit": 20
}

这比把所有条件编码成 ?status=paid&status=shipped&customer.country=CN... 更稳,也比用 POST /orders/search 更能表达“我只是在查”。

可以这样实践:在 Node.js 里接收 QUERY

现实问题是:RFC 发布不代表所有框架、网关、CDN、浏览器立刻支持。落地时建议先从服务端和内部 API 开始试点,并保留兼容方案。

下面是一个最小 Node.js 示例,用原生 http 模块接收 QUERY 请求。保存为 server.js 后即可运行。

const http = require("http");

function readBody(req) {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", chunk => {
      body += chunk;
      if (body.length > 1024 * 1024) {
        req.destroy();
        reject(new Error("request body too large"));
      }
    });
    req.on("end", () => resolve(body));
    req.on("error", reject);
  });
}

const server = http.createServer(async (req, res) => {
  if (req.method !== "QUERY" || req.url !== "/orders/search") {
    res.writeHead(404, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: "not found" }));
    return;
  }

  try {
    const rawBody = await readBody(req);
    const query = rawBody ? JSON.parse(rawBody) : {};

    const result = {
      method: req.method,
      received: query,
      data: [
        { id: "ord_1001", status: "paid", total: 199 },
        { id: "ord_1002", status: "shipped", total: 299 }
      ]
    };

    res.writeHead(200, {
      "Content-Type": "application/json",
      "Cache-Control": "private, max-age=30"
    });
    res.end(JSON.stringify(result, null, 2));
  } catch (error) {
    res.writeHead(400, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: error.message }));
  }
});

server.listen(3000, () => {
  console.log("listening on http://localhost:3000");
});

运行和调用:

node server.js

curl -X QUERY 'http://localhost:3000/orders/search' \
  -H 'Content-Type: application/json' \
  -d '{
    "status": ["paid", "shipped"],
    "created_after": "2025-01-01T00:00:00Z",
    "limit": 20
  }'

如果你的 HTTP 客户端、代理或 API 网关暂时不接受自定义/新方法,可以先设计一个降级入口:

curl -X POST 'http://localhost:3000/orders/search' \
  -H 'Content-Type: application/json' \
  -H 'X-HTTP-Method-Override: QUERY' \
  -d '{"status":["paid"],"limit":20}'

这不是最终形态,但能帮助团队在协议生态完全跟上之前先统一 API 语义。

采用时要检查这些边界

QUERY 很有用,但不是无脑替换 GETPOST

  • 简单资源读取继续用 GET:例如 GET /users/123 没必要改。
  • 会改变状态的操作继续用 POSTPUTPATCHDELETE:不要因为 body 方便就滥用 QUERY
  • 检查网关和中间件:负载均衡、WAF、API Gateway、日志采集、SDK 生成器可能还不认识 QUERY
  • 明确缓存策略:虽然语义上适合查询,但实际缓存需要服务端、中间件、客户端共同支持。
  • 控制 body 大小:复杂查询不等于无限查询,仍要限制请求体、分页、排序字段和过滤复杂度。

我的建议:先用在“复杂只读查询”上

最适合试点 QUERY 的地方,是搜索、报表、审计日志、订单筛选、数据分析这类接口:它们只读、条件复杂、经常超过 URL 舒适区,又不该被建模成会改变状态的 POST

落地路径可以很务实:新内部 API 优先支持 QUERY;外部 API 同时提供 POST + X-HTTP-Method-Override 兼容;网关、监控、SDK 都确认支持后,再把它作为正式公开契约。QUERY 的价值不在于制造一个新潮动词,而是让 HTTP API 的语义终于少一点别扭。


相关推荐