- 后端
【免费下载链接】graffle
Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.
导读
本文围绕 Graffle(Simple GraphQL Client for JavaScript)HTTP 传输中的methodMode: 'getReads'方法模式展开,讲解如何让 query / subscription 等读类型操作通过 HTTP GET 发送、同时让 mutation 写操作继续走 HTTP POST 的完整配置方法。读完本文你将掌握methodMode两种取值(post/getReads)的语义差异、GET 模式下请求参数如何编码进 URL 搜索参数、底层方法选择逻辑,以及如何借助 anyware 中间件验证最终发出的请求。
本文核心素材来自仓库中的 method-get 示例文档、示例源码 及其 输出记录,底层实现参考 TransportHttp 扩展源码 与 HTTP 编码实现。
一、getReads模式解决什么问题
HTTP 传输默认情况下所有请求都通过 HTTP POST 发送(这也是 TransportHttp 配置默认值 中methodMode: 'post'的含义)。但在某些场景下,我们希望读操作(query、subscription)走 HTTP GET,从而获得 GET 的缓存友好、可分享 URL 等特性,同时保持写操作(mutation)走 POST。
Graffle 的methodMode配置提供了两种取值(见 MethodMode 常量定义):
| 取值 | 行为 |
|---|---|
post(默认) | 所有操作均通过 HTTP POST 发送,请求体为 JSON |
getReads | 读类型操作(query、subscription)通过 HTTP GET 发送,写类型操作(mutation)仍通过 HTTP POST 发送 |
二、最小可运行示例
以下完整示例来自 examples/10_transport-http/transport-http_method-get.ts,它创建一个 Graffle 客户端,配置methodMode: 'getReads',并通过 anyware 中间件打印出每次请求的完整信息:
import { Graffle } from 'graffle' import { publicGraphQLSchemaEndpoints, show } from '../$/helpers.js' const graffle = Graffle .create() .transport({ url: publicGraphQLSchemaEndpoints.Pokemon, methodMode: `getReads`, // [!code highlight] headers: { tenant: `nano` }, }) .anyware(async ({ exchange }) => { show(exchange.input.request) return await exchange() }) // The following request will use an HTTP POST method because it is // using a "mutation" type of operation. await graffle.gql('mutation { addPokemon(attack:0, defense:0, hp:1, name:"Nano", type: grass) { name } }').$send() // The following request will use an HTTP GET method because it // is using a "query" type of operation. await graffle.gql('query { pokemonByName(name: "Nano") { hp } }').$send()其中publicGraphQLSchemaEndpoints.Pokemon来自 examples/$/helpers.ts,默认指向http://localhost:3000/graphql,也可以通过环境变量POKEMON_SCHEMA_URL覆盖。
示例中.anyware()的作用是在请求发出前拦截并打印exchange.input.request(即最终构造的 Request 对象),这是验证方法模式是否生效的最直接手段。
三、输出验证:mutation 走 POST,query 走 GET
示例运行后产生的实际输出保存在 examples/outputs/10_transport-http/transport-http_method-get.output.txt,两份输出清晰地展示了两种请求的差异。
第一份输出对应 mutation(addPokemon),虽然methodMode是getReads,但请求仍然走 POST,且请求体为 JSON:
{ methodMode: 'getReads', headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', tenant: 'nano' }, method: 'post', url: { _tag: 'url', value: URL { href: 'http://localhost:3000/graphql', ... } }, body: '{"query":"mutation { addPokemon(attack:0, defense:0, hp:1, name:\\"Nano\\", type: grass) { name } }"}' }第二份输出对应 query(pokemonByName),请求走 GET,且 GraphQL 文档被编码进 URL 搜索参数query:
{ methodMode: 'getReads', headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', tenant: 'nano' }, method: 'get', url: { _tag: 'url', value: URL { href: 'http://localhost:3000/graphql?query=query+%7B+pokemonByName%28name%3A+%22Nano%22%29+%7B+hp+%7D+%7D', ... searchParams: URLSearchParams { 'query' => 'query { pokemonByName(name: "Nano") { hp } }' }, } } }从两份输出可以总结出 GET / POST 请求的差异:
- method 字段:mutation 为
post,query 为get; - 请求头:POST 请求带
accept与content-type: application/json,GET 请求只带accept(没有content-type,因为请求体为空); - 载荷位置:POST 请求将 GraphQL 文档放在 JSON body 中,GET 请求将 GraphQL 文档作为
query搜索参数拼接到 URL 上; - URL 形态:GET 请求的 URL 会带
?query=...搜索串,如http://localhost:3000/graphql?query=query+%7B+pokemonByName...。
四、底层原理:方法选择与载荷编码
4.1 方法选择的判定逻辑
getReads并不是简单地"读操作全走 GET",其底层判定逻辑位于 TransportHttp.ts 的 run 函数:
const methodMode = input.transport.methodMode const requestMethod = methodMode === MethodMode.post ? `post` : GraphqlKit.Schema.OperationType.LookupToAccessKind[operationType] === `read` ? `get` : `post`从源码结构可以看出:
- 若
methodMode为post,则一律使用 POST; - 若
methodMode为getReads,则根据操作的访问类型(AccessKind)判断:读操作(read,即 query / subscription)用 GET,写操作(write,即 mutation)仍用 POST; operationType从请求的 operation 字段解析而来(Str.is(input.request.operation)判断是字符串还是结构化对象)。
4.2 GET 的搜索参数编码
GET 请求的载荷编码由 getRequestEncodeSearchParameters 实现,它把 GraphQL 请求编码为 URL 搜索参数对象:
export const getRequestEncodeSearchParameters = (request: RequestConfig): Record<string, string> => { return { query: request.query, ...(request.variables ? { variables: JSON.stringify(request.variables) } : {}), ...(request.operationName ? { operationName: request.operationName } : {}), } }也就是说,GET 模式下支持三个搜索参数:query(GraphQL 文档)、variables(变量,JSON 序列化)、operationName(操作名),仅在存在时才编码。随后这些搜索参数会通过Http.SearchParams.appendAll/appendAllToPath拼接到 URL 上(TransportHttp.ts)。
4.3 POST 的 JSON body 编码
POST 请求的载荷由 postRequestEncodeBody 实现,将query、variables、operationName序列化为 JSON 字符串作为请求体。
4.4 请求头差异
两类请求的默认请求头也由 HTTP 编码实现 区分:
export const postRequestHeadersRec = { accept: ACCEPT_REC, 'content-type': CONTENT_TYPE_REC, } export const getRequestHeadersRec = { accept: ACCEPT_REC, }其中ACCEPT_REC为application/graphql-response+json; charset=utf-8, application/json; charset=utf-8,CONTENT_TYPE_REC为application/json,均遵循 GraphQL Over HTTP 规范 的媒体类型约定(对应代码注释见 http/__.ts)。这正是示例输出中 GET 请求没有content-type头的原因。
五、methodMode 的三种配置途径与优先级
methodMode与url、headers、raw一样属于 TransportHttp 的配置项(见 ConfigurationInput 定义),可通过三种途径配置(见 TransportHttp 文档):
- 构造函数中:
.transport({ methodMode: 'getReads', ... }); with方法中:动态切换配置;- 扩展栈中:通过扩展追加配置。
配置优先级从高到低为:扩展栈(后注册的扩展优先)→with配置 → 构造函数配置;其中raw配置优先级高于transport下其他平级属性。
5.1 显式配置 POST
如果需要显式恢复默认的 POST 行为,可以显式声明:
import { Graffle } from 'graffle' Graffle .create() .transport({ methodMode: 'post', })5.2 显式配置 getReads
import { Graffle } from 'graffle' Graffle .create() .transport({ methodMode: 'getReads', })六、url 与 headers 配置细节
在getReads模式下,url的配置同样支持多种形态(TransportHttp.ts):
- 绝对 URL 字符串:
https://api.example.com/graphql - 相对路径字符串:
/api/graphql、./graphql、../graphql(浏览器环境或框架增强 fetch 场景下有用) - URL 对象
需要留意的是,Node.js 原生 fetch 不支持相对 URL,相对路径仅在浏览器或提供增强 fetch 的框架(如 SvelteKit)中有效。配置解析在配置期即完成校验(parseURLInput会在 URL 非法时提前抛出错误),避免运行时才暴露问题。
headers采用合并策略(Http.Headers.mergeInitWithStrategyMerge,见 TransportHttp.ts),且若某个 header 被赋空字符串值,会删除先前设置的该 header。
七、注意事项
methodMode: 'getReads'只影响 HTTP 方法选择,不会改变操作本身的性质:mutation 永远走 POST,这是 示例文档 与源码共同确认的行为;- GET 模式下 GraphQL 文档会完整出现在 URL 中,若文档包含敏感信息,请结合业务场景评估是否适合使用 GET;
transport.raw可以直接透传原生fetch配置(如{ mode: 'cors' }),但由于没有护栏,若在raw.method中设置PATCH之类的值会覆盖methodMode的判定结果,产生不符合 GraphQL Over HTTP 规范的请求(TransportHttp 文档 中的 Raw 一节有明确提醒);- 示例输出验证方式可复用:通过
.anyware()打印exchange.input.request,即可在任意环境中确认方法模式是否按预期生效。
八、延伸阅读
- TransportHttp 扩展文档:完整的配置说明、相对 URL 用法、raw 配置与 ware 钩子说明;
- TransportHttp 实现源码:
methodMode判定、URL 解析、请求构造的完整逻辑; - HTTP 编码与解码实现:GET 搜索参数编码、POST body 编码、请求头与响应解析;
- method-get 示例输出:示例实际运行产生的请求快照;
- 同目录下其他 HTTP 传输示例:raw、abort、headers 等,可对照理解 TransportHttp 的完整能力面。
- 后端
【免费下载链接】graffle
Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.
相关推荐
cpp-httplib 流式上传实战:用 ContentProviderWithoutLength 发送 HTTP Chunked 传输正文
cpp httplib 流式上传实战:用 ContentProviderWithoutLength 发送 HTTP Chunked 传输正文 本文基于 cpp
后端网络Graffle 请求取消实战:使用 AbortController 中断 GraphQL HTTP 请求
Graffle 请求取消实战:使用 AbortController 中断 GraphQL HTTP 请求 本文讲解如何在 Graffle(一个极简、可扩展、类型
后端Graffle多传输支持详解:HTTP与内存传输的最佳实践
Graffle多传输支持详解:HTTP与内存传输的最佳实践 Graffle作为一款现代化的GraphQL客户端,其最强大的特性之一就是 多传输支持 。无论您是需
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考