首页 / 文章

面向前端团队的HTTP查询方法:带请求体的安全读取方式

了解在复杂过滤场景下何时使用 HTTP QUERY 方法优于 GET 和 POST,如何通过 fetch 调用它,以及需要哪些 CORS、缓存和基础设施支持。

发布者:REACTAPP.TOP阅读约 7 分钟1546 词

来源: HTTP QUERY: What Frontend Developers Should Know

搜索和筛选界面存在一个反复出现的 API 问题:如果将每个筛选条件都编码到 GET 查询字符串中,URL 会变得冗长且难以使用;而如果通过 POST 请求以 JSON 格式发送筛选条件,又会使用一种本应用于状态变更的机制来处理实际上只是读取操作的任务。HTTP QUERY 方法正是为解决这一缺陷而设计的。本文将解释 QUERY 的含义、如何使用 fetch 在 React 应用中发送 QUERY 请求,以及在使用该机制之前,从 CORS 到缓存等各个环节需要注意什么。

QUERY 解决的问题

当产品列表需要多个筛选条件时,很快就会产生类似如下的请求:

/products?category=laptop&brand=Apple&minPrice=50000&maxPrice=150000&rating=4

这种方式虽然可行,但每新增一个筛选条件都会使字符串变长;对于嵌套或重复的数值(如多个品牌、价格范围、排序顺序、分页信息等),还需要特定的编码规则,而且过长的 URL 可能会超出服务器、代理和日志系统的长度限制。

常用的解决方案是使用带有 JSON 数据体的 POST 请求。这样在客户端实现起来更简洁,但会使得接口规范变得混乱。POST 既不安全也不具备幂等性,因此中间节点无法确定该请求可以重复发送,缓存默认也不会存储响应内容,任何读取 API 的人都必须查阅文档才能知道这个特定的 POST 请求仅用于读取数据。

QUERY 是什么

QUERY 是一种 HTTP 方法,用于请求服务器执行请求内容中描述的查询操作。据撰写本文时的报道,IETF 已于 2026 年 6 将其作为 RFC 10008 标准发布;在引用该标准之前,请先查看 RFC 索引以了解其当前状态。客户端不会将参数塞进 URL 中,而是将其放在请求体中发送:

QUERY /products HTTP/1.1
Content-Type: application/json
{
  "category": "laptop",
  "brands": ["Apple", "Dell"],
  "price": {
    "min": 50000,
    "max": 150000
  }
}

服务器会解析该查询并返回匹配的结果。QUERY与POST的区别在于其语义:它被定义为安全的,即不会修改目标资源;同时还是可重试的,意味着发送两次与发送一次的效果相同。正是这两个特性使得客户端、代理和缓存能够将其视为读取操作。例如,可重试性意味着客户端或中间节点可以自动重试失败的QUERY请求,而不会引发重复的副作用,而对于POST则不应如此操作。

GET、POST与QUERY的选择

QUERY并不会取代其他方法,每种方法都有其明确的用途:

  • GET仍然是用于简单获取数据的默认方式:通过ID获取资源,或使用少量简单参数获取简短列表。
  • POST 仍适用于那些会创建或修改服务器状态、或者不适合重复执行的操作。
  • QUERY 则适合那些输入较为复杂、需要结构化数据的读取操作,例如嵌套过滤器、值数组或查询表达式。
  • 对前端而言,其优势在于复杂的过滤器状态可以直接映射到请求体中,而且 API 规范明确说明不会对任何数据进行修改。

    从 React 发送 QUERY 请求

    假设有一个 React 组件,它将当前的产品过滤器存储在一个对象中:

    const filters = {
      category: "laptop",
      brands: ["Apple", "Dell"],
      price: {
        min: 50000,
        max: 150000
      },
      rating: 4
    };
    

    使用 QUERY 时,该对象可以直接被序列化并原样发送。下面的辅助函数使用了带有自定义方法名及 JSON 请求体的 fetch:

    const searchProducts = async (filters) => {
      const response = await fetch("/api/products", {
        method: "QUERY",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(filters)
      });
    

    同一函数的后半部分会检查响应状态并解析结果:

      if (!response.ok) {
        throw new Error("Failed to fetch products");
      }  return response.json();
    };
    

    除了方法字符串之外,这与POST请求完全相同。区别仅在于语义而非语法:该请求现在被定义为查询类型。需要注意两个实际细节:Fetch标准仅将少数知名方法名大写,因此务必始终以大写字母书写"QUERY"。与GET和HEAD不同,fetch函数不允许这些方法携带请求体,而QUERY则允许携带。

    在用户界面方面存在一个权衡点:编码在URL中的过滤器可以添加书签、分享,并且在页面重新加载后依然有效,而QUERY的请求体则不具备这些特性。如果分享链接很重要,那么你可能仍需要在客户端将过滤器状态反映到页面URL中,同时再将结构化查询发送给API。

    检查整个请求路径的支持情况

    即便某项方法已实现标准化,也并不意味着所有处理请求的组件都能理解它。浏览器发送的请求在到达应用程序之前会经过多层处理,其中任何一层都可能拒绝或篡改不熟悉的方法。在启用 QUERY 之前,请务必验证:

    • 您所支持的浏览器及其 fetch() 行为。
    • API 的 CORS 配置。
    • API 网关和反向代理。
    • CDN 和边缘缓存。
    • 身份验证与授权中间件。
    • 监控、日志记录和追踪工具。
    • 后端框架的路由处理及请求体解析功能。

    某些层可能只允许使用固定的方法列表,而有些层则会对无法识别的方法直接丢弃请求体。请在与生产环境相似的环境中进行端到端测试,而不仅仅是针对本地服务器进行测试。

    CORS 总是需要预检请求

    QUERY 不在 CORS 安全方法列表(GET、HEAD 和 POST)之中。这意味着浏览器发起的跨源 QUERY 请求总会触发预检请求:在真正发送请求之前,浏览器会先发送一个 OPTIONS 请求,询问是否允许使用 QUERY。

    如果你的 React 应用与 API 位于不同的域名下,服务器必须通过包含 QUERY 以及请求所使用的其他头部信息(如 Content-Type)的 Access-Control-Allow-Methods 头部来响应该预检请求。对于 POST 请求,由于已有 JSON 体存在,也会强制触发预检请求,因此额外的开销通常并不大,但该方法名必须明确列出。

    在开发过程中很容易忽略这一点。Postman 和 curl 等工具并不强制实施 CORS 规则,因此某个接口在那些工具中可能运行正常,但在浏览器中却会失败。

    可以缓存,但更为复杂

    QUERY响应可以被缓存,这是它相比POST的主要优势之一。问题在于响应不仅取决于URL,还取决于请求体。试想以下两个发送到同一端点的请求体:

    {
      "brand": "Apple"
    }
    
    {
      "brand": "Dell"
    }
    

    它们必须返回不同的结果。因此,缓存在判断是否可以重用已存储的响应时,必须在缓存键中包含请求内容以及其媒体类型。缓存还可能对内容进行规范化处理(例如忽略JSON中无关紧要的空白字符),以便相同的查询能共享同一个缓存条目。

    对于React和Next.js开发者来说,关键是要在缓存处理时不要将QUERY视为“带请求体的GET”。浏览器HTTP缓存、CDN以及框架级的fetch缓存可能还无法根据请求体来生成缓存键,因此应确认每一层的实际行为,而非直接假设能命中缓存。

    QUERY与GraphQL的关系

    GraphQL经常在相同的讨论中出现,但两者运作于不同的层面。GraphQL提供了一种描述所需数据的语言以及用于解析这些数据的模型。而QUERY仅属于传输层级的操作指令:它规定了携带查询内容的请求应如何被HTTP处理。目前GraphQL客户端通常使用POST发送操作请求;从原理上讲,GraphQL服务器也可以通过QUERY接受读取操作,但QUERY并不能取代GraphQL,两者可以根据API的设计共同存在。

    是否应该迁移现有的API?

    通常不需要,至少不能仅仅因为存在QUERY就这么做。如果某个搜索端点使用GET或POST已经运行良好,更换传输方式只会增加迁移和兼容性方面的工作,却带来的好处有限。只有在设计新的端点且其请求具有以下特点时,使用QUERY才最为合理:

    • 仅用于读取。
  • 可能较为复杂。
  • 以结构化内容的形式表达比用URL参数更自然。
  • 借助安全、可重试的语义特性,如重试和缓存,可能会带来好处。
  • 当GET请求变得难以处理时,它为团队提供了一种有原则的替代方案,不必总是使用POST。

    关键要点

    • 对于简单的数据获取操作,使用GET,并保持简单请求的简洁性。
    • 对于需要结构化内容的复杂只读请求,可考虑使用QUERY。
    • 不要期望速度提升:QUERY只是改变了请求的表示方式,并不会影响后端或网络的速度。
    • 在投入生产使用前,需确认浏览器、代理、网关、CDN以及后端框架是否支持该功能。
    • 配置CORS以明确允许该请求方法,并在真实浏览器中进行测试。
    • 在依赖缓存后的QUERY响应之前,务必确认请求体中的每一个缓存层键值。
    • 关注生态系统的采用情况:搜索页面、控制面板以及数据量较大的应用是QUERY最先发挥作用的领域。