☰
Graffle 实战:用 transport 配置精确控制 GraphQL 请求头(静态 Headers、空值取消与 anyware 动态注入)
2026/10/10 5:27:41 网站建设 项目流程
  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载

Graffle 是一个"极简、可扩展、类型安全、随处运行"的 JavaScript GraphQL 客户端。本文围绕仓库中 Headers 示例 与 Dynamic Headers 示例 展开,讲解如何通过transport配置控制 HTTP 请求头,包括静态设置、用空字符串取消已设置的请求头、通过raw.headers透传底层RequestInit,以及借助anyware扩展在每个请求上动态注入请求头。读完你将掌握 Graffle 请求头配置的完整玩法与底层合并原理。

示例总览:两个互补的 Headers 用法

transport-http_headers主题在仓库中对应两个示例,它们覆盖了请求头控制的两种主流场景:

  • Headers(静态配置):transport-http_headers_raw__headers.ts —— 在构建客户端时一次性声明请求头,并用空字符串"撤销"先前设置的请求头。
  • Dynamic Headers(动态注入):transport-http_extension_headers__dynamicHeaders.ts —— 通过anyware扩展在每次请求发出前动态改写请求头(例如追加时间戳)。

这两个示例的网站页面(headers.md 与对应页面)由脚本 generate-docs.ts 从示例源码自动生成,包括代码块与真实运行输出,可直接对照验证。

静态请求头:transport 配置的 headers 字段

先看核心示例 transport-http_headers_raw__headers.ts 的完整代码:

import { Graffle } from 'graffle' import { show } from '../$/helpers.js' import { publicGraphQLSchemaEndpoints } from '../$/helpers.js' const graffle = Graffle .create() .transport({ url: publicGraphQLSchemaEndpoints.Pokemon, headers: { authorization: `Bearer MY_TOKEN`, 'x-something-to-unset': `true`, }, raw: { headers: { 'x-from-raw': `true`, }, }, }) .transport({ headers: { 'x-something-to-unset': `` }, }) .anyware(({ exchange }) => { if (exchange.input.transportType !== `http`) return exchange() show(exchange.input.request.headers) return exchange() }) await graffle.gql('{ pokemons { name } }').$send()

代码做了三件事:

  1. 通过.transport({ url, headers, raw })为 HTTP 传输声明基础配置:authorization: Bearer MY_TOKEN与x-something-to-unset: true两个请求头,raw.headers额外声明x-from-raw: true。
  2. 再次调用.transport({ headers: { 'x-something-to-unset': `` } }),用一个空字符串请求头把先前设置的x-something-to-unset取消。
  3. 用anyware观察请求发出前的实际请求头(仅拦截transportType === 'http'的交换)。

运行输出:请求头合并后的真实形态

示例对应的运行输出见 transport-http_headers_raw__headers.output.txt:

---------------------------------------- SHOW ---------------------------------------- Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', 'x-from-raw': 'true', authorization: 'Bearer MY_TOKEN' }

从中可以看到三个关键事实:

  • Graffle 自动为请求添加了accept: application/graphql-response+json; charset=utf-8, application/json; charset=utf-8与content-type: application/json两个默认请求头(与 TransportHttp.ts 中postRequestHeadersRec的默认合并逻辑一致)。
  • x-from-raw: true(来自raw.headers)与authorization(来自headers)都成功进入最终请求。
  • 被空字符串"取消"的x-something-to-unset没有出现在最终请求头中,验证了"空字符串请求头会取消先前设置的请求头"这一行为。

空字符串取消机制:配置合并的源码依据

为什么空字符串能"取消"请求头?这并非魔法,而是 HTTP 传输配置器在配置合并阶段就完成的:当新配置的某个请求头值为空字符串时,合并策略用空值覆盖旧值,最终在构造RequestInit时该请求头不再被包含。

源码依据位于 TransportHttp.ts 的inputResolver:

return { methodMode: input.methodMode ?? current.methodMode, raw: input.raw ?? current.raw, url, headers: Http.Headers.mergeInitWithStrategyMerge(current.headers, input.headers), }
  • 每次调用.transport(config)都会执行一次合并:current.headers是已累积的配置,input.headers是本次传入的新配置,二者经Http.Headers.mergeInitWithStrategyMerge合并后写回配置状态。
  • 因为请求头是可累积合并而非整体替换,所以示例中第二次.transport()只传headers而不传url时,url仍保留第一次配置的 Pokemon 端点。
  • 同理,raw也是逐层合并的:raw: input.raw ?? current.raw保证只覆盖本次显式给出的字段。

这种"链式合并 + 空值取消"的设计,让请求头配置具备很强的组合性:你可以在一个地方设置公共请求头(如认证),在另一个地方按需覆盖或撤销个别请求头。

配置项速查

transport配置中与请求头相关的字段(类型定义见 TransportHttp.ts 的ConfigurationInput):

字段类型说明
headersHeadersInit请求头配置,支持普通对象;多次调用会与既有请求头合并,空字符串值表示取消该请求头
raw.headersHeadersInit透传到底层RequestInit.headers,用于覆盖需要低层控制、且不属于 Graffle 高层抽象的场景
raw(其余字段)RequestInit底层fetch的RequestInit透传,如mode: 'cors'(参见 transport-http_raw.ts)

另外,transport方法本身是不可变的:它会返回一个新客户端实例,原客户端不被修改;如果配置没有产生有效变化,则出于性能考虑返回同一实例(见 transport.ts 的类型注释)。

动态请求头:用 anyware 在每个请求上注入 Headers

静态配置适合"请求头始终不变"的场景;但真实项目中,请求头往往依赖每次请求的上下文(如时效性的 token、追踪 ID、时间戳)。Graffle 的anyware扩展机制正是为此设计。看 transport-http_extension_headers__dynamicHeaders.ts:

import { Graffle } from 'graffle' import { publicGraphQLSchemaEndpoints, show } from '../$/helpers.js' const graffle = Graffle .create() .transport({ url: publicGraphQLSchemaEndpoints.Pokemon, }) .anyware(({ exchange }) => { if (exchange.input.transportType !== `http`) return exchange() return exchange({ input: { ...exchange.input, request: { ...exchange.input.request, headers: [ ...new Headers(exchange.input.request.headers), [`X-Sent-At-Time`, Date.now().toString()], ], }, }, }) }) .anyware(({ exchange }) => { show(exchange.input.request) return exchange() }) await graffle.gql('{ pokemons { name } }').$send()

要点拆解:

  • exchange(input)用于对请求进行"就地改造"后继续传递;exchange()不带参数则原样放行。
  • 改造方式是把请求头先展开为Headers实例,再追加X-Sent-At-Time: <当前毫秒时间戳>这一对新条目。
  • 因为请求头在交换管道中是一个可展开的Headers数据结构(见 TransportHttp.ts 中ExchangePostRequest/ExchangeGetRequest类型),所以可以安全地展开、追加、再重建。
  • 两个anyware按注册顺序构成管道:第一个负责注入时间戳,第二个负责打印改造后的完整请求。

运行输出:动态头确实进入了请求

对应的输出见 transport-http_extension_headers__dynamicHeaders.output.txt:

---------------------------------------- SHOW ---------------------------------------- { methodMode: 'post', headers: [ [ 'accept', 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8' ], [ 'content-type', 'application/json' ], [ 'X-Sent-At-Time', '1762136942519' ] ], method: 'post', ... body: '{"query":"{ pokemons { name } }"}' }

输出证明:在anyware管道中,请求头已从"普通对象形态"变为Headers条目数组形态([key, value]元组),X-Sent-At-Time被成功追加到 Graffle 默认请求头之后。这也意味着你可以用同样的方式实现 per-request 的动态认证头、链路追踪头等。

实战建议与组合技巧

综合两个示例与源码,给出几条可直接落地的实践建议:

  1. 静态 + 动态结合:把不变的基础请求头(如content-type之外的业务头、基础认证头)放进.transport({ headers });把每次请求都变化的头(时间戳、trace id、短期 token)放进anyware中动态注入。
  2. 按层拆分配置:利用"多次.transport()累积合并"的特性,把 URL、公共请求头、raw低层选项分开配置,代码更清晰,且后续覆盖单个请求头时不会影响其他配置。
  3. 撤销请求头用空字符串:当上游(如框架预设或公共配置)设置了某个请求头、而当前请求希望不带它时,传headers: { 头名: '' }即可,不必重建整个客户端。
  4. 低层需求走raw:需要控制mode、credentials、signal等标准RequestInit字段时,统一放入raw(参考 transport-http_raw.ts 中对raw: { mode: 'cors' }的用法)。
  5. 用 anyware 做请求头审计:像两个示例那样挂一个只读anyware打印exchange.input.request.headers,可以低成本实现请求头日志与调试。

延伸阅读

  • 完整的 HTTP 传输配置项说明与ConfigurationInput/ConfigurationNormalized类型定义:TransportHttp.ts
  • 请求头合并的实现入口(mergeInitWithStrategyMerge)与默认请求头组装逻辑:TransportHttp.ts
  • transport方法的不可变语义与重载形式:transport.ts
  • 更多 HTTP 传输主题示例(abort、custom fetch、method-get、raw 等):examples/10_transport-http
  • 网站示例页面的自动生成逻辑(含 Twoslash 代码块与输出快照):generate-docs.ts
  • 后端

【免费下载链接】graffle

Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.

项目地址:https://gitcode.com/gh_mirrors/gr/graffle
点击查看免费下载
上一篇:CVPR 2023冠军方案QCNet:多智能体轨迹预测的终极指南
下一篇:Ani代码重构案例:如何改进遗留代码

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询