ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

tRPC v10 客户端自定义请求头(Headers)权威指南:动态 Authorization 与登录认证实战

2026/9/9 20:54:43 拓冰建站 浏览量
tRPC v10 客户端自定义请求头(Headers)权威指南:动态 Authorization 与登录认证实战 tRPC v10 客户端自定义请求头Headers权威指南动态 Authorization 与登录认证实战【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc导读在 tRPC 应用中身份认证、追踪 ID、国际化等场景几乎都离不开在请求中携带自定义 HTTP 请求头。本篇指南以 tRPC v10 官方文档 客户端自定义请求头 为核心系统讲解如何通过httpBatchLink与httpLink的headers配置注入自定义头并深入trpc/client源码剖析对象与函数两种形态、每次请求动态调用的底层实现。读完本文你将能够独立实现一个基于内存 token 登录 mutation 动态刷新Authorization头的完整认证方案并理解 batch 批量请求与请求头合并的细节原理。说明本仓库中 version-10.x 版本文档 面向 tRPC v10仓库主分支的 packages/client 及 最新版 headers 文档 已是 v11/v12 代码但headers的核心 API 与设计一脉相承下文会同时对照两版文档并给出源码级依据。一、headers配置的使用位置与两种形态1.1 只有 HTTP 终结链路支持headersheaders选项并非所有 link 都支持它只能配置在使用HTTP 终结链路terminating link时。在 v10 中即 httpBatchLink 与 httpLink 两个链路仓库当前主分支在此基础上扩展到了httpBatchStreamLink、httpSubscriptionLink但 v10 文档明确限定前两者The headers option can be customized in the config when using thehttpBatchLinkor thehttpLink.因此如果你在自定义的非终结 link如loggerLink、splitLink的判定分支中找不到该配置属正常现象——请求头最终只在真正发出 HTTP 请求的那一层被合并。1.2 两种形态静态对象 与 动态函数headers可以是普通对象也可以是函数官方文档给出的关键区别是headerscan be both an object or a function. If its a function it will get called dynamically for every HTTP request.// 静态对象整段客户端生命周期内不变 headers: { x-app-version: 1.0.0, }, // 动态函数每次 HTTP 请求发出前都被调用一次 headers() { return { Authorization: token, }; },函数形态的价值在于每次请求都能取到最新值——例如内存中的 token 在登录后更新下一次查询自动携带新凭证这正是 v10 认证场景的核心用法。二、完整示例动态注入Authorizationv10 Next.jsv10 文档给出的标准示例位于 Next.js 页面的utils/trpc.ts使用createTRPCNext聚合 Router 类型并装配httpBatchLink// Import the router type from your server file import type { AppRouter } from /server/routers/app; import { httpBatchLink } from trpc/client; import { createTRPCNext } from trpc/next; let token: string; export function setToken(newToken: string) { /** * You can also save the token to cookies, and initialize from * cookies above. */ token newToken; } export const trpc createTRPCNextAppRouter({ config(config) { return { links: [ httpBatchLink({ url: http://localhost:3000/api/trpc, /** * Headers will be called on each request. */ headers() { return { Authorization: token, }; }, }), ], }; }, });几个值得注意的细节url前缀v10 Next.js 版统一走/api/trpcPages Router 下由pages/api/trpc/[trpc].ts处理若为独立部署的服务端则如http://localhost:3000/api/trpc所示。注释即意图源码注释明示 Headers will be called on each request与上文每次请求动态调用相印证。闭包持有 tokenheaders()通过闭包读取模块级token变量因此无需把 token 传入链路构造处任何模块调用setToken后后续请求立即生效。若使用非 Next.js 的纯客户端可把示例中的createTRPCNext替换为 v10 的createTRPCProxyClient二者对links数组的配置方式一致。仓库中大量 examples 展示了这种用法例如 express-minimal 的客户端、standalone-server 的客户端 等。三、登录后更新 token认证闭环实战headers()动态取值的函数本身不含状态写入逻辑token 更新需在登录 mutation 成功后完成。v10 文档配套了认证页示例const loginMut trpc.auth.login.useMutation({ onSuccess(opts) { token opts.accessToken; }, });整个闭环如下用户提交表单触发trpc.auth.login.useMutation()服务端auth.login校验通过后返回accessTokenonSuccess回调将accessToken写入模块级token即示例中的setToken内部逻辑客户端任意后续 query/mutation 发出 HTTP 请求前headers()重新执行并读到新 token注入Authorization服务端中间件据此完成鉴权。关于 token 的存放v10 文档留了充分的自由度Thetokencan be whatever you want it to be. Its entirely up to you whether thats just a client-side variable that you update the value of on success or whether you store the token and pull it from local storage.即三种常见策略均可存储策略优点注意事项模块级内存变量示例默认实现最简单页面刷新前一直有效刷新后丢失需重新登录localStorage/sessionStorage刷新后仍可恢复需在headers()内自行读取并处理 XSS 风险Cookie可配合credentials或 CSRF 防护需服务端与客户端域名约定一致若选择 Cookie可在模块初始化时从 Cookie 中读取并赋给token与文档注释 You can also save the token to cookies, and initialize from cookies above 相呼应。四、headers的类型签名与取值范围源码级4.1HTTPHeaders的两种合法形态在 packages/client/src/links/types.ts 中HTTPHeaders被定义为一个联合类型export type HTTPHeaders | HeadersInitEsque | Recordstring, string[] | string | undefined;其中HeadersInitEsque是可迭代的键值对集合即任何[Symbol.iterator]()产出[key, value]迭代器的结构等价于浏览器Headers或[[k,v]]数组interface HeadersInitEsque { [Symbol.iterator](): IterableIterator[string, string]; }也就是说返回对象既可以是普通字面量{ Authorization: token }也可以是[[Authorization, token]]之类的迭代结构。该类型定义了两处细节值允许string | string[] | undefined因此可一次性给同一头名传多值如多组Set-Cookie风格场景显式允许undefined便于条件性传头如仅当 token 存在时才设置。4.2 各链路上headers回调的参数差异在仓库当前主分支的 HTTPBatchLinkOptions.ts 中批量链路的回调会收到整批操作列表headers?: | HTTPHeaders | ((opts: { opList: NonEmptyArrayOperation }) HTTPHeaders | PromiseHTTPHeaders);而单请求httpLink的回调只收到当前这一个操作op见 httpLink 相关类型。这带来一个进阶技巧在batch 模式下headers()能拿到opList即同批发出的一组操作含各自的id、type、path、input可以根据批内包含的 procedure 路径做差异化头注入——例如批量中混有公开接口与受保护接口时动态决定是否携带凭证。同时回调支持返回PromiseHTTPHeaders允许在发请求前异步刷新 token如检测到过期自动续期。Operation结构定义在 types.ts含id、typemutation | query | subscription、input、path、context、signal。4.3headers之外的 HTTP 选项参考headers只是HTTPLinkOptions的字段之一见 httpBatchLink.md 中的接口export interface HTTPLinkOptions { url: string; fetch?: typeof fetch; // fetch ponyfill AbortController?: typeof AbortController | null; // AbortController ponyfill headers?: | HTTPHeaders | ((opts: { op: Operation }) HTTPHeaders | PromiseHTTPHeaders); }httpBatchLink在此基础上额外叠加maxURLLength?: number与当前主分支新增的maxItems单批最大操作数默认Infinity。若请求体而非请求头超限或需要按请求类型分流可参考同目录 splitLink.mdx 与loggerLink。五、底层实现函数形态为何能每次请求都调用从仓库源码可以清晰看到headers 按请求求值的完整链路。所有 HTTP 链路最终都会把headers归一化为函数形态——即使传入对象也会被包成返回该对象的函数见 httpBatchLink.ts、httpLink.ts 中对headers()的包装调用。随后进入 internals/httpUtils.ts 的fetchHTTPResponseexport async function fetchHTTPResponse(opts: HTTPRequestOptions) { throwIfAborted(opts.signal); const url opts.getUrl(opts); const body opts.getBody(opts); const method opts.methodOverride ?? METHOD[opts.type]; const resolvedHeaders await (async () { const heads await opts.headers(); // ① 每次发请求前求值 if (Symbol.iterator in heads) { // ② 迭代器形态转普通对象 return Object.fromEntries(heads); } return heads; })(); const headers { ...(opts.contentTypeHeader method ! GET ? { content-type: opts.contentTypeHeader } : {}), ...(opts.trpcAcceptHeader ? { [opts.trpcAcceptHeaderKey ?? trpc-accept]: opts.trpcAcceptHeader } : undefined), ...resolvedHeaders, // ③ 自定义头最后展开优先级最高 }; return getFetch(opts.fetch)(url, { method, signal, body, headers, }); }可提炼出三个关键事实每次请求必求值fetchHTTPResponse位于请求真正发出的必经路径opts.headers()在每次 fetch 前执行①与文档get called dynamically for every HTTP request一一对应且因支持await函数体可异步。迭代器形态被归一化源码用Symbol.iterator in heads判断并Object.fromEntries转换②因此无论返回对象还是Headers/键值对数组都能正确处理。合并优先级确定tRPC 会先放业务必需头GET 请求不加content-type仅在非 GET 时注入另有trpc-accept头用于声明响应类型自定义头最后展开③因此你的headers返回值可覆盖 tRPC 的默认头。5.1 与请求头安全相关的健壮性细节同一函数中先调用throwIfAborted(opts.signal)——若调用方已通过AbortController中止请求则不会执行后续headers()求值与网络 IO避免无谓的令牌读取或服务端请求。请求中止/超时的客户端处理可进一步参考 fetchHTTPResponse 前置实现。六、常见问题与排查建议现象原因分析解决方向登录成功后仍返回 401headers()读取的是旧闭包值未走setToken/onSuccess 更新确认 token 在 mutationonSuccess后写入同一模块变量batch 请求本身仍是一次 HTTP 请求头在发请求前求值无需担心批内错乱服务端收不到自定义头CORS浏览器跨域预检不通过服务端在 CORS 配置中将所需头加入allowedHeaders如Authorization并在exposedHeaders中按需暴露响应头tRPC 服务端 CORS 细节见 服务端 adapters 相关文档想加的头不属于认证、又恒定不变没必要用函数直接传静态对象即可减少每次请求的函数调用开销需要区分 GET/POST 或按需放行头httpLink支持 GET参照 httpLink 文档 中的请求方式说明并结合headers条件返回想对是否携带 token精细控制值类型允许undefined无 token 时返回{ Authorization: undefined }可借助类型中的undefined分支实现条件化头批量 URL 过长导致 413/414batch 把多个操作拼进 URL为httpBatchLink设置maxURLLength超限自动拆分批量详见 httpBatchLink.md关于批量请求本身若需彻底禁用批处理官方提供两条路径——服务端batching: { enabled: false }或客户端将httpBatchLink换回httpLink每操作一次 HTTP 请求。二者在 httpBatchLink.md 中有可直接复制的完整示例需要逐请求动态头、且希望避免批量语义时直接选用httpLink更直观。七、小结与延伸阅读tRPC v10 的headers配置是客户端 HTTP 链路的一等公民传对象即静态注入传函数则每次请求动态求值并支持异步返回配合登录 mutation 的onSuccess即可低成本完成 token 刷新与Authorization注入。在源码层httpUtils.ts可确认自定义头在每次 fetch 前求值、支持迭代器形态输入、并最后展开以覆盖默认头。如需进一步研究仓库内值得对照阅读的路径本文依据的 v10 文档客户端 headers 文档、最新版 headers 文档链路选项与类型HTTPBatchLinkOptions.ts、links 类型定义底层请求实现httpUtils.tsv10 链路文档httpBatchLink.md、httpLink.md、links 概览配套可运行示例查看 examples/minimal、examples/express-minimal 等目录下的客户端组装方式可在本地复现httpBatchLink({ url, headers() { ... } })的完整客户端配置。【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考