ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TanStack Start 服务端运行时深度指南:`@tanstack/start-server-core` 的请求处理、请求/响应工具与会话管理

TanStack Start 服务端运行时深度指南:`@tanstack/start-server-core` 的请求处理、请求/响应工具与会话管理 TanStack Start 服务端运行时深度指南tanstack/start-server-core的请求处理、请求/响应工具与会话管理【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router导读本文围绕 TanStack Start 的服务端运行时核心包tanstack/start-server-core展开完整讲解createStartHandler请求处理器的创建与三阶段请求分派机制、基于 AsyncLocalStorage 的请求/响应工具函数getRequest、setResponseHeader、Cookie 与 Session 管理以及生产环境下的安全规则。读完本文你将能够独立编写src/server.ts服务端入口、在 Server Function 与 Server Route 中安全读写请求数据、管理加密会话并规避服务端工具被误用于客户端等常见坑位。适用前提本文以当前仓库TanStack Start / Router 单仓库中的packages/start-server-core实现为准框架示例以 React 为主Solid、Vue 的用法完全一致仅包名不同例如tanstack/solid-start/server、tanstack/vue-start/server。一、包定位服务端专属运行时tanstack/start-server-core是 TanStack Start 的服务端运行时核心提供四类能力请求处理器createStartHandler处理所有入站请求请求工具读取请求getRequest系列、设置响应setResponseHeader系列Cookie 工具getCookies/setCookie/deleteCookie会话管理useSession/getSession/updateSession/clearSession等加密会话能力。关键设计是这些工具通过 AsyncLocalStorage 与当前请求绑定在一次请求的整个调用栈内随处可用无需层层传参。从源码可以看到request-response.ts使用全局Symbol.for(tanstack-start:event-storage)注册一个共享的AsyncLocalStorageStartEvent实例并在每次请求时执行eventStorage.run({ h3Event }, () handler(request, requestOpts))见 request-response.ts。CRITICAL这些工具是SERVER-ONLY的必须从tanstack/framework-start/server导入而不是主入口。在非服务端请求上下文调用会直接抛错源码中getH3Event()在找不到存储时会抛出No StartEvent found in AsyncLocalStorage...。CRITICAL类型是完全推断的不要强制 cast、不要给推断值写类型注解。CRITICAL请在活跃的请求内部读取 Cookie、Header、请求 URL 与运行时环境变量绝不要在模块作用域捕获它们——边缘运行时可能按请求注入这些值且并发请求绝不能共享请求派生状态。二、创建请求处理器createStartHandlercreateStartHandler创建处理所有入站请求的主处理器内部按服务端函数 → 服务端路由 → 应用 SSR三个阶段处理详见第六节。2.1 最小用法// src/server.ts // 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createStartHandler } from tanstack/react-start/server import { defaultStreamHandler } from tanstack/react-start/server export default createStartHandler({ handler: defaultStreamHandler, })createStartHandler支持两种调用形态只传回调函数向后兼容或传{ handler, transformAssets, ... }配置对象。源码 createStartHandler.ts 会判断cbOrOptions是函数还是对象从而提取handler与FinalManifestOptions资产清单相关配置。2.2 资产 URL 变换CDNexport default createStartHandler({ handler: defaultStreamHandler, transformAssets: https://cdn.example.com, })transformAssets支持字符串统一前缀或函数形式函数可获得{ kind, url }并按请求动态变换。真实仓库中 css-inline 示例 演示了完整用法根据kind css-url给样式 URL 追加cdn1查询参数并在 handler 级配置inlineCss: false作为默认值同时允许请求级覆盖。2.3 与运行时适配层集成createStartHandler返回的RequestHandler需要在具体运行时中导出通常用createServerEntry包装见 default-entry/server.ts// e2e/react-start/css-inline/src/server.ts节选 import { createStartHandler, defaultStreamHandler } from tanstack/react-start/server import { createServerEntry } from tanstack/react-start/server-entry const handler createStartHandler({ handler: defaultStreamHandler, inlineCss: false, }) export default createServerEntry({ fetch(request) { // 请求级选项可以覆盖 handler 级默认值 return handler(request, { inlineCss: request.headers.get(x-inline-css) ! false, }) }, })RequestHandler的签名见 request-handler.ts为(request: Request, opts?: RequestOptions) PromiseResponse | Response其中RequestOptions支持onEarlyHintsHTTP 103 提前提示、responseLinkHeader将收集的 Link 附加到最终响应头供不支持 103 的运行时/CDN 兜底以及inlineCss仅当构建启用了server.build.inlineCss时生效默认true。三、请求工具读请求、写响应所有工具均从tanstack/framework-start/server导入在一次请求处理中随处可用无需传参。3.1 读取请求数据// 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createServerFn } from tanstack/react-start import { getRequest, getRequestHeaders, getRequestHeader, getRequestIP, getRequestHost, getRequestUrl, getRequestProtocol, } from tanstack/react-start/server const serverFn createServerFn({ method: GET }).handler(async () { const request getRequest() const headers getRequestHeaders() const auth getRequestHeader(authorization) const ip getRequestIP({ xForwardedFor: true }) const host getRequestHost() const url getRequestUrl() const protocol getRequestProtocol() return { ip, host } })各函数的底层行为均委托给 h3-v2见 request-response.ts函数说明可选参数getRequest()返回当前请求的Request对象—getRequestHeaders()返回类型化的请求头TypedHeadersRequestHeaderMap—getRequestHeader(name)按名读取单个请求头无则返回undefined—getRequestIP(opts)获取请求 IPxForwardedFor: true时信任X-Forwarded-For仅当应用位于可信 CDN/反代之后才启用getRequestHost(opts)获取请求主机名无 host 头默认localhostxForwardedHost: true时优先使用x-forwarded-hostgetRequestUrl(opts)获取完整入站 URLxForwardedHost/xForwardedProto控制是否采纳对应转发头getRequestProtocol(opts)获取协议http/https无法判断时默认httpxForwardedProto: false禁用转发头判断3.2 设置响应数据// 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createServerFn } from tanstack/react-start import { setResponseHeader, setResponseHeaders, setResponseStatus, getResponseHeaders, getResponseHeader, getResponseStatus, removeResponseHeader, clearResponseHeaders, } from tanstack/react-start/server const serverFn createServerFn({ method: POST }).handler(async () { setResponseStatus(201) setResponseHeader(x-custom, value) setResponseHeaders({ cache-control: no-store }) return { created: true } })实现细节request-response.tssetResponseHeader(name, value)value支持string | Arraystring数组时先删除同名头再逐条append以支持多值头如多个Set-CookiesetResponseStatus(code?, text?)通过 h3 的sanitizeStatusCode/sanitizeStatusMessage消毒getResponseStatus()无状态时默认返回200removeResponseHeader(name)删除单个头clearResponseHeaders(headerNames?)传入数组则只清除指定头否则清空全部响应头另外还有getResponse()可拿到底层 h3H3Event的响应对象。四、Cookie 管理// 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createServerFn } from tanstack/react-start import { getCookies, getCookie, setCookie, deleteCookie, } from tanstack/react-start/server const serverFn createServerFn({ method: POST }).handler(async () { const allCookies getCookies() const token getCookie(session-token) setCookie(preference, dark, { httpOnly: true, secure: process.env.NODE_ENV production, sameSite: lax, maxAge: 60 * 60 * 24 * 30, // 30 天 path: /, }) deleteCookie(old-cookie) })getCookies()解析Cookie请求头返回去除了undefined值的纯对象内部使用Object.create(null)见 request-response.tsgetCookie(name)按名取值setCookie(name, value, options?)委托 h3 的setCookieoptions为标准CookieSerializeOptionscookie-es提供常用字段httpOnly、secure、sameSitestrict | lax | none、maxAge秒、path、domain、expiresdeleteCookie(name, options?)通过 h3 删除 Cookie。五、会话管理Cookie 中的加密会话会话以加密形式存储在 Cookie中必须提供password进行加密。5.1 会话配置function getSessionConfig() { const password process.env.SESSION_SECRET if (!password || password.length 32) { throw new Error(SESSION_SECRET must be at least 32 characters) } return { password, name: my-app-session, maxAge: 60 * 60 * 24 * 7, cookie: { httpOnly: true, secure: process.env.NODE_ENV production, sameSite: lax as const, path: /, }, } }SessionConfig的完整字段来自 session.ts选项类型默认值说明passwordstring必填会话令牌加密密钥namestringstartCookie 名称getDefaultSessionConfig合并的默认值maxAgenumberundefined过期时间秒cookiefalse \| CookieSerializeOptionsundefinedCookie 设置默认即安全默认值secure、httpOnly、/sessionHeaderfalse \| stringx-start-session/x-{name}-session会话响应头sealSealOptions—自定义加密封装算法加密算法aes-128-ctr/aes-256-cbc、完整性算法sha256、盐长度默认 256、密码最小长度默认 32 等cryptoCrypto—自定义 Crypto 实现generateId() stringCrypto.randomUUID会话 ID 生成器sealOptions中minPasswordlength默认 32与上面 32 字符校验相呼应加密默认aes-256-cbc完整性校验默认sha256TTL 默认 0永不过期配合maxAge控制。5.2 完整会话流程读取、更新、清除// 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createServerFn } from tanstack/react-start import { useSession, getSession, updateSession, clearSession, } from tanstack/react-start/server type SessionData { userId?: string } // 读取会话未登录返回 null const getUser createServerFn({ method: GET }).handler(async () { const session await useSessionSessionData(getSessionConfig()) if (!session.data.userId) { return null } return db.users.findById(session.data.userId) }) // 更新会话登录 const login createServerFn({ method: POST }) .validator((data: unknown) { if ( typeof data ! object || data null || !(email in data) || typeof data.email ! string || data.email.trim().length 0 || !(password in data) || typeof data.password ! string || data.password.length 0 ) { throw new Error(Invalid credentials) } return { email: data.email.trim().toLowerCase(), password: data.password } }) .handler(async ({ data }) { const user await db.users.findByEmail(data.email) const passwordHash user?.passwordHash ?? getDummyPasswordHash() const passwordMatches await verifyPassword(data.password, passwordHash) if (!user || !passwordMatches) { throw new Error(Invalid credentials) } await updateSessionSessionData(getSessionConfig(), { userId: user.id }) return { success: true } }) // 清除会话退出登录 const logout createServerFn({ method: POST }).handler(async () { await clearSession(getSessionConfig()) return { success: true } })getDummyPasswordHash()用于恒定时间比较防时序攻击当用户不存在时仍用虚拟哈希执行同样的密码校验避免通过响应时间差异探测账号是否存在。生产环境请用与真实密码哈希相同的算法和成本参数预计算该哈希并放入环境变量DUMMY_PASSWORD_HASH仓库原文档 SKILL.md 亦强调此点。5.3 会话管理器方法const session await useSession{ userId: string }(config) session.id // 会话 IDstring | undefined session.data // 会话数据类型化 await session.update({ userId: 123 }) // 持久化会话数据 await session.clear() // 清除会话数据5.4 生产环境会话规则Cookie 中只放小而无关紧要的数据存稳定的会话/用户 ID然后在每个受保护请求中从权威存储加载最新权限与账号状态需要吊销、设备追踪、大数据量或即时角色变更时使用服务端会话记录Cookie 中只放其不透明 ID登录后、权限变更后、改密后、退出登录后都应轮换rotate会话生产环境使用HttpOnly、SameSite、Path/与Secure仅当同时满足Secure、无Domain、Path/时才在生产环境使用__Host-前缀的 Cookie 名清除会话时使用与设置时相同的 Cookie 名称与路径测试覆盖登录、带认证的刷新、过期、退出登录以及旧 Cookie 重放。六、查询参数校验getValidatedQuery使用Standard Schema校验查询字符串参数// 请为你的框架使用 tanstack/framework-startreact / solid / vue import { getValidatedQuery } from tanstack/react-start/server import { z } from zod const serverFn createServerFn({ method: GET }).handler(async () { const query await getValidatedQuery( z.object({ page: z.coerce.number().default(1), limit: z.coerce.number().default(20), }), ) return { page: query.page } })注意getValidatedQuery接收的是Standard Schema 校验器对象而不是回调函数底层为 h3 的getValidatedQuery见 request-response.ts。因此 zod、valibot、arktype 等实现 Standard Schema 规范的库都可以直接传入。当前仓库提供 zod-adapter、valibot-adapter、arktype-adapter 等适配器包供选择。七、请求处理原理三阶段分派createStartHandler按三个阶段处理请求源码主线见 createStartHandler.ts阶段 1Server Function 分派若 URL 匹配服务端函数前缀/_serverFn对应构建常量TSS_SERVER_FN_BASE则反序列化载荷FormData / 查询串 / JSON见 server-functions-handler.ts运行全局请求中间件含默认的 CSRF 保护执行对应服务端函数返回序列化结果使用 seroval 序列化支持RawStream流式结果与分帧协议TSS_CONTENT_TYPE_FRAMED_VERSIONED。值得注意的细节GET 请求的载荷通过查询参数payload传递且上限 1MBMAX_PAYLOAD_SIZE 1_000_000超限抛Payload too large防止 DoS方法不匹配时在解析载荷前就返回 405并带Allow响应头CSRF 保护若构建时检测到请求中间件中未包含createCsrfMiddleware开发环境会给出一次警告提示在src/start.ts中注册 CSRF 中间件或通过tanstackStart({ serverFns: { disableCsrfMiddlewareWarning: true } })关闭警告源码 createStartHandler.ts。阶段 2Server Route 处理器非 Server Function 请求按 URL 匹配定义了server.handlers的路由收集并去重路由级中间件跳过已在请求阶段执行过的运行与 HTTP 方法匹配的处理器handlers[method] ?? handlers[ANY]处理器可返回Response或调用next()落入 SSR。两个规范细节源码 createStartHandler.tsHEAD 回退按 RFC 9110 §9.3.2HEAD 必须与 GET 返回相同的头字段但不含 body优先级为HEAD处理器 →GET→ANY最后手段最终会剥离响应体延迟到 SSR仅当路由定义了component时才允许调用next()落入应用渲染否则抛You cannot defer to the app router if there is no component defined on this route开发环境提示生产环境为Internal Server Error。阶段 3App Router SSR加载所有路由 loader为客户端水合**脱水dehydrate**状态含请求级资产清单注入调用 handler 回调如defaultStreamHandler渲染 HTML。此外还有若干全局细节协议相对 URL如//posts会先被归一化并重定向308仅接受Accept包含text/html或*/*的请求否则返回 500 JSON 错误重定向统一经handleRedirectResponse处理内部路径必须使用绝对路径href或to且服务端重定向不支持函数式的params/search/hash带x-tsr-serverFn头的 Server Function 重定向会序列化为 JSON 交给客户端处理中间件执行器会处理流式响应的生命周期disposeStreamResponse、请求中止时的资源清理并在finally中清理路由 SSR 状态见 createStartHandler.ts。八、常见错误与规避1. CRITICAL在客户端代码中导入服务端工具服务端工具依赖 AsyncLocalStorage只在服务端请求处理期间可用。在客户端代码中导入会导致构建错误或运行时崩溃。// 错误 —— 在组件文件客户端执行中导入 import { getCookie } from tanstack/react-start/server function MyComponent() { const token getCookie(auth) // 在客户端崩溃 } // 正确 —— 只在 Server Function 内部使用 // 请为你的框架使用 tanstack/framework-startreact / solid / vue import { createServerFn } from tanstack/react-start import { getCookie } from tanstack/react-start/server const getAuth createServerFn({ method: GET }).handler(async () { return getCookie(auth) })2. HIGH大多数会话操作忘记密码useSession、getSession、updateSession、sealSession都需要password字段用于加密缺失时运行时直接抛错。仅clearSession接受PartialSessionConfig清除时密码可选。3. MEDIUM生产环境未使用 HTTPS 会话生产环境会话 Cookie 应使用secure: true。默认 Cookie 选项可能不会强制这一点默认值为 secure/httpOnly//但你的部署若为 http 协议需要显式确认配置一致。4. CRITICAL在模块作用域捕获请求或环境状态不要在模块加载时用process.env创建会话配置也不要把getRequest()、请求头、Cookie 或会话数据缓存在模块变量中。应在 handler 或中间件回调内部创建配置、读取请求状态。这是边缘运行时按请求隔离环境的要求也能防止跨请求数据泄漏与第一节的 CRITICAL 原则一致。九、延伸阅读server-functions 技能文档——创建使用这些工具的服务端函数middleware 技能文档——请求中间件含 CSRFserver-routes 技能文档——服务端路由处理器start-server-core 源码目录——createStartHandler.ts请求分派、request-response.ts请求/响应工具与 AsyncLocalStorage、session.ts会话类型、server-functions-handler.tsServer Function 分派与序列化css-inline 端到端示例——createStartHandlertransformAssets 请求级选项覆盖的完整落地样例【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表