请求发送与持久化查询


请求发送与持久化查询

GraphQL的HTTP传输方式

GraphQL规范没有绑定特定的传输协议,但HTTP是最常见的载体。GraphQL over HTTP规范(2021年发布)定义了标准的传输方式。

POST请求:最常用的方式。查询文本放在请求体的 query 字段中,变量放在 variables 字段中。

curl -X POST https://api.example.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer eyJhbGci..." \ -d '{ "query": "query GetUser($id: ID!) { user(id: $id) { name email } }", "variables": { "id": "1001" } }' ​

GET请求:规范允许只读查询通过GET发送,参数编码在URL中。这在需要HTTP缓存(CDN、浏览器缓存)的场景下有用。

GET /graphql?query=%7B%20user(id%3A%20%221001%22)%20%7B%20name%20%7D%20%7D ​

实际项目中GET方式很少用——因为查询文本可能很长,URL长度有限制,而且大多数查询都使用变量,把变量也编码到URL中会很笨重。

multipart请求:文件上传使用multipart/form-data编码,遵循graphql-multipart-request-spec规范。

fetch API发送请求

不使用任何客户端库,原生fetch就能发GraphQL请求:

async function graphqlQuery(query: string, variables?: Record<string, any>) { const response = await fetch('https://api.example.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}`, }, body: JSON.stringify({ query, variables }), }); const result = await response.json(); if (result.errors) { throw new Error(result.errors[0].message); } return result.data; } // 使用 const data = await graphqlQuery( 'query GetUser($id: ID!) { user(id: $id) { name email posts { title } } }', { id: '1001' } ); ​

这种方式简单直接,适合小型项目或不需要缓存的场景。但它在生产环境中有一个问题:每次请求都要发送完整的查询文本,网络传输开销不小,而且每次响应都需要手动处理错误和缓存。

持久化查询(Persisted Queries)

持久化查询解决了"每次请求都发送完整查询文本"的问题。核心思路是:提前把所有查询文本注册到服务器,分配一个唯一的ID(通常是hash值),客户端只发送查询ID,服务器根据ID查找对应的查询文本执行。

// 客户端发送 { "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "ecf4edb4646e4e3e8d3b5e0d0a6b4c8d..." } }, "variables": { "id": "1001" } } // 服务器收到hash,查找对应的查询文本 // 如果找不到,返回: { "errors": [{ "message": "PersistedQueryNotFound" }] } // 客户端收到错误后,回退到发送完整查询文本 { "query": "query GetUser($id: ID!) { user(id: $id) { name email } }", "variables": { "id": "1001" }, "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "ecf4edb4646e4e3e8d3b5e0d0a6b4c8d..." } } } // 服务器存储这个查询文本和hash的映射 // 后续请求只需发送hash ​

这个机制叫APQ(Automatic Persisted Queries),Apollo Client内置支持。它的好处:

减少网络传输:查询文本不再每次发送,只发一个hash字符串。对于复杂的查询(几百字节到几KB),这能显著减少请求体大小。

安全防护:服务器可以配置为只接受已注册的查询,拒绝任意查询文本。这从根本上防止了恶意查询攻击(深度嵌套、超大批量等),因为攻击者无法发送自定义查询。

CDN缓存友好:因为查询文本是固定的(通过hash确定),响应可以基于hash+variables进行缓存。

APQ的实现通常在Apollo Server中使用 @apollo/gateway 或独立中间件,客户端用Apollo Client 3.x自动处理hash计算和回退逻辑。

图:GraphQL请求生命周期

图:GraphQL请求生命周期

请求生命周期

一个完整的GraphQL请求生命周期包括:

  1. 客户端构建查询(或从缓存/代码生成中获取)
  2. 计算查询hash(持久化查询模式)
  3. HTTP POST发送请求
  4. 服务器解析查询文本
  5. 验证查询合法性
  6. 执行查询(调用解析器链)
  7. 组装JSON响应
  8. 客户端接收响应,处理数据和错误

每一步都有优化的空间。持久化查询优化了步骤2和4;DataLoader优化了步骤6;客户端缓存(下一节讲)优化了步骤1——甚至可以跳过整个请求周期。

请求头的注意事项

GraphQL请求通常需要设置以下HTTP头:

头部 用途
Content-Type 固定为 application/json
Authorization Bearer令牌认证
Accept 指定响应格式(通常不需要设置)
X-Apollo-Require-Preflight APQ相关,首次发送需要preflight
apollographql-client-name 客户端标识,用于监控
apollographql-client-version 客户端版本,用于兼容性检查

apollographql-client-name 和 apollographql-client-version 这两个自定义头部不是规范要求的,但Apollo生态中广泛使用——它们让服务端知道请求来自哪个客户端应用和版本,方便做A/B测试、兼容性检查和使用量分析。

批量请求与并发控制

GraphQL 规范允许在一个 HTTP 请求里提交多个操作,服务端按顺序或并行执行后返回一个数组。但实践中批量请求用得很少——它让错误处理复杂化,也让缓存失效,性能收益又有限。多数场景下一个请求一个操作更清晰。

前端并发控制则是另一个问题:一个页面可能同时触发多个查询,如果全部并发放出,后端压力大、移动端电量消耗也高。Apollo Client 的 useQuery 会对同一文档的重复请求做去重,短时间内的相同查询只发一次。更精细的控制可以用 Link 中间件实现请求合并,或在后端对同一用户的请求做限流。

常见问题排查

返回 403 或 401。检查 Authorization 头是否在 Link 或 fetch 配置里正确注入;GraphQL 端点通常不接受把 token 放在 URL 参数里。

返回 400 但查询看起来没问题。多数是请求体格式问题——query 字段必须是字符串,不能是对象;变量必须是扁平 JSON;操作名拼写要与文档里的操作名完全一致。

中文乱码。确保 Content-Type 是 application/json 且携带 UTF-8 编码,查询文本里的中文注释和字符串在绝大多数服务端默认按 UTF-8 处理。

超时与重试。GraphQL 请求没有内置重试语义,客户端需要自己决定重试策略。幂等的查询可以安全重试;Mutation 重试前要确认操作是否已经生效,配合 clientMutationId 或服务端幂等键避免重复扣款、重复下单这类事故。

作者与出处
原作者:灏天文库
来源:平台策划编纂
整理人: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U