尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

H3 官方实战示例完全指南:Cookie、Session、静态资源、流式响应与数据校验

H3 官方实战示例完全指南:Cookie、Session、静态资源、流式响应与数据校验 H3 官方实战示例完全指南Cookie、Session、静态资源、流式响应与数据校验【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3本文围绕 H3 官方文档 docs/4.examples 中的六类实战示例展开Cookie 处理、HTTPQUERY方法、会话Session、静态资源服务、流式响应与数据校验。读完本文你将掌握 H3 事件处理器中最常用的六大场景的完整写法、底层原理与可运行的完整代码并了解如何结合 examples/ 目录中的可运行示例快速上手。引言官方示例能带给你什么H3 是一个为高性能与可移植性而生的极简 HTTP 框架其官方文档在 docs/4.examples/0.index.md 中汇总了一组“常见用法示例”Cookies —— 用 Cookie 在客户端存储数据HTTP QUERY Method —— 在请求体中携带查询条件的安全只读请求Session —— 用 Session 记住用户Static Assets —— 服务 HTML、图片、CSS、JavaScript 等静态资源Streaming Response —— 向客户端流式发送数据Validation —— 在数据处理前校验数据的形状与合法性这六个主题恰好覆盖了 Web 服务开发中最常见、最刚需的能力。本文将以这些文档为主体逐一向你讲解每个示例的完整代码、配置参数、适用场景并结合仓库中的源码实现如 src/utils/cookie.ts、src/utils/session.ts、src/utils/static.ts与可运行示例examples/cookies.mjs、examples/query.mjs 等做纵深剖析。需要说明的是H3 官方仓库在 examples/ 目录下提供了更多可直接运行的文件如 examples/auth.mjs、examples/router.mjs、examples/websocket.mjs 等文档中用::read-more组件指向了这些目录你可按需查看。一、处理 Cookie在 H3 中处理 Cookie 非常直观共有三个工具函数见 docs/4.examples/handle-cookie.md 与源码 src/utils/cookie.tssetCookie把 Cookie 附加到响应上getCookie从请求中读取 CookiedeleteCookie从响应中清除 Cookie。1.1 设置 Cookie在事件处理器中调用setCookie即可import { setCookie } from h3; app.use(async (event) { setCookie(event, name, value, { maxAge: 60 * 60 * 24 * 7 }); return ; });setCookie的第四参是配置项对应 Set-Cookie 响应头 的各种 Cookie 标志flag均是可选的选项作用maxAge设置 Cookie 的过期时间单位为秒上例为 7 天expires用Date对象设置 Cookie 的过期时间path设置 Cookie 的路径domain设置 Cookie 的域名secure设置Secure标志仅 HTTPS 传输httpOnly设置HttpOnly标志禁止脚本访问sameSite设置SameSite标志strict/lax/none仓库中的可运行示例 examples/cookies.mjs 演示了完整用法访问/set时写入 Cookie访问/时读取并回显import { H3, serve, getCookie, setCookie } from h3; export const app new H3(); app .get(/, (event) { const testCookie getCookie(event, testCookie); return testCookie is ${JSON.stringify(testCookie)} (go to /set to set it); }) .get(/set, (event) { // By default, path is set to /. You can use any of the options supported by the Set-Cookie header. setCookie(event, testCookie, bar, { httpOnly: true }); return TestCookie is set. Go back to / to see it!; }); serve(app);示例中的注释提到path默认为/并且Set-Cookie头支持的所有选项都可以直接传入。运行node examples/cookies.mjs后访问http://localhost:3000即可体验。1.2 读取 Cookie读取 Cookie 同样简单import { getCookie } from h3; app.use(async (event) { const name getCookie(event, name); // do something... return ; });如果 Cookie 存在getCookie返回其值否则返回undefined。1.3 删除 Cookie删除 Cookie 使用deleteCookieimport { deleteCookie } from h3; app.use(async (event) { deleteCookie(event, name); return ; });从源码看deleteCookie实际上是setCookie的一个包装把值设置为、maxAge设置为0从而让客户端立即使该 Cookie 过期失效。这样即可把 Cookie 从客户端擦除。二、HTTPQUERY方法在请求体中携带查询HTTPQUERY方法RFC 10008与GET一样安全safe、幂等idempotent、可缓存cacheable但它的查询条件放在请求体中并带有Content-Type。它是“我想要一个 GET但查询条件太大或太结构化、放不进 URL”时的标准答案。H3 将QUERY作为一等公民方法支持可通过app.query()注册处理器底层等价于app.on(QUERY, …)并提供了两个辅助工具函数。关于路由层面的完整说明可参考 docs/1.guide/1.basics/2.routing.md 中的 “HTTP QUERY Method” 小节。2.1 注册QUERY处理器像处理POST一样读取请求体即可import { readBody } from h3; app.query(/books, async (event) { const query await readBody(event, { type: text }); return runSearch(query); });[!NOTE] 由于QUERY携带的是可被攻击者控制的请求体因此与POST一样会应用请求体大小限制。2.2 宣告可接受的查询格式使用appendAcceptQuery告知客户端某个资源支持哪些查询格式。它会设置Accept-Query响应头一个 Structured Fields List并且也可以在普通的GET上设置让客户端在发送QUERY之前先发现可用的格式import { appendAcceptQuery } from h3; app.get(/books, (event) { appendAcceptQuery(event, [application/sql, application/jsonpath]); // Accept-Query: application/sql, application/jsonpath return Send a QUERY request with a SQL or JSONPath body.; });2.3 校验Content-Type使用requireContentType强制实施 RFC 的错误语义它返回匹配到的媒体类型或在缺失时抛出400、不支持时抛出415、格式错误时抛出422import { requireContentType, readBody } from h3; app.query(/books, async (event) { const type requireContentType(event, [application/sql, application/jsonpath]); const query await readBody(event, { type: text }); return runQuery(type, query); });2.4 提供可缓存的GET替代方案QUERY的响应无法用 URL 定位因此浏览器和 CDN 无法缓存它。RFC 10008 建议通过Content-Location响应头把客户端指向一个等价的、可缓存的GET端点把查询结果存放在稳定的 id 下客户端即可用普通的、HTTP 可缓存的GET重复该查询app.query(/books, async (event) { const result runQuery(type, query); const id queryId(type, query); // stable hash of the query cache.set(id, result); event.res.headers.set(content-location, /books/${id}); return result; });2.5 完整可运行示例仓库中的 examples/query.mjs 是一个自包含、可直接运行的完整演示一个/books资源接受 SQL 风格与 JSONPath 风格的查询校验Content-Type并宣告可缓存的GET替代方案同时还在/上提供了一个小型交互页面。可用node examples/query.mjs本地运行。它的核心逻辑包括用 FNV-1a 哈希生成稳定查询 idqueryId把每次查询结果存入Map缓存/books/:id端点从缓存取出结果并设置cache-control: public, max-age60让浏览器/CDN 可以缓存/books的QUERY处理器则依次执行appendAcceptQuery→requireContentType→readBody({ type: text })→ 计算 id 并设置Content-Location。你还可以用curl从终端体验# 发现可接受的查询格式 curl -i http://localhost:3000/books # - Accept-Query: application/sql, application/jsonpath # SQL 查询注意响应中的 Content-Location 头 curl -i -X QUERY http://localhost:3000/books \ -H Content-Type: application/sql \ --data SELECT * FROM books WHERE author Simpson # - 200, Content-Location: /books/id # 通过可缓存的 GET 替代方案重新获取同一结果 curl -i http://localhost:3000/books/id # - 200, Cache-Control: public, max-age60 # JSONPath 查询 curl -X QUERY http://localhost:3000/books \ -H Content-Type: application/jsonpath \ --data $[?(.year2015)] # 不支持的格式 curl -i -X QUERY http://localhost:3000/books -H Content-Type: text/plain --data x # - 415 Unsupported Media Type[!NOTE] 与GET不同QUERY不在CORS 的安全列表safelist中浏览器会先发送预检请求preflight。如果你给handleCors传了显式的methods白名单请把QUERY加进去。另外从路由文档可知QUERY在条件缓存上与GET同等对待可通过handleCacheHeaders返回304proxy转发QUERY时也会带上其请求体。三、Session用会话记住用户会话Session是一种通过 Cookie 记住用户的方式是认证用户或保存其偏好如语言、个性化设置的常见手段。H3 为此提供了丰富的工具函数见 docs/4.examples/handle-session.mduseSession初始化一个会话返回控制它的包装器getSession初始化或获取当前用户的会话updateSession更新当前会话的数据clearSession清除当前会话。大多数时候你只需要useSession就足够了因为它在底层会自动调用其余几个工具。3.1 初始化一个会话import { useSession } from h3; app.use(async (event) { const session await useSession(event, { password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); // do something... });这会初始化一个会话并返回一个名为h3的 CookieSet-Cookie响应头内容经过加密。如果请求中带有名为h3的 Cookie 或名为x-h3-session的请求头会话将以该 Cookie/请求头中的内容初始化。[!NOTE] 请求头优先于 Cookie。[!WARNING]password会对每个会话 Cookie 进行密封seal它的熵才是真正的安全边界。被盗的会话 Cookie 会以明文携带盐与完整性摘要因此弱密码或可猜测的密码可以被离线暴力破解——提高 PBKDF2 迭代次数只能减缓破解速度无法修复低熵密钥的问题。请始终从密码学安全随机源生成密码例如node -e console.log(require(node:crypto).randomBytes(32).toString(base64))上文示例使用硬编码值是为了可读性真实应用中请从环境变量如process.env.SESSION_PASSWORD加载至少 32 字符的随机生成的密钥且切勿提交到版本控制。可猜测的口令即使超过 32 个字符也不安全。3.2 从会话读取数据数据存放在会话的data属性中如果没有数据则为空对象{}。读取仍使用useSession底层调用getSessionimport { useSession } from h3; app.use(async (event) { const session await useSession(event, { password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); return session.data; });3.3 向会话写入数据写入仍使用useSession底层调用updateSession。下面是一个经典的“访问计数器”示例import { useSession } from h3; app.use(async (event) { const session await useSession(event, { password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); const count (session.data.count || 0) 1; await session.update({ count: count, }); return count 0 ? Hello world! : Hello world! You have visited this page ${count} times.; });流程是先从请求中获取会话若不存在则新建然后把count属性加一并通过session.update()写回最后返回访问次数。多次刷新页面即可看到计数递增。[!NOTE] 如果用curl之类的 CLI 工具测试由于 CLI 不保存 Cookie计数不会递增。你需要从响应中取出 Cookie 并在后续请求中回传。3.4 清除会话清除会话仍使用useSession底层调用clearSessionimport { useSession } from h3; app.use(/clear, async (event) { const session await useSession(event, { password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); await session.clear(); return Session cleared; });H3 会发送一个名为h3的空 Cookie 的Set-Cookie响应头来清除会话。3.5 配置选项useSession的第二个参数是配置对象除password外所有选项都是可选的import { useSession } from h3; app.use(async (event) { const session await useSession(event, { name: my-session, password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, cookie: { httpOnly: true, secure: true, sameSite: strict, }, maxAge: 60 * 60 * 24 * 7, // 7 days }); return session.data; });值得单独说明的是name选项它决定了存储会话的 Cookie 名称默认为h3。H3 还会从一个由name派生的请求头读取会话该请求头被规范化为小写的x-${name.toLowerCase()}-session所以默认名称h3对应x-h3-session请求头即使name写成混合大小写如MyApp请求头仍解析为小写的x-myapp-session而 Cookie 保留原始大小写。这正是前文示例中 Cookie 名为h3的原因。[!NOTE] 会话 Cookie 默认值为secure: true、httpOnly: true、sameSite: lax、path: /。均可通过cookie选项覆盖。[!NOTE]secure: true会告诉浏览器仅在 HTTPS 下存储和发送 Cookie。本地开发使用纯 HTTP 时合规浏览器尤其是 Safari 和 iOS以及某些本地域名下的 Chrome会静默丢弃该 Cookie导致会话无法持久化。本地开发时请设置cookie: { secure: false }解决。3.6 过期机制会话有两套相互独立的过期控制可以单独使用也可以同时使用maxAge是绝对生命周期从会话创建时起算无论用户多活跃都会到期idleTimeout是滑动生命周期从最后一次请求起算。活跃用户保持登录闲置用户被登出。const session await useSession(event, { password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, idleTimeout: 60 * 30, // signed out after 30 minutes of inactivity... maxAge: 60 * 60 * 24 * 7, // ...and after 7 days regardless });设置idleTimeout后H3 通过重新密封reseal会话 Cookie 来前移空闲窗口并把重新密封的时间戳写入其中。createdAt保持不变这正是maxAge仍能作为硬性上限的原因。Cookie 的Expires设为两者中先到期的那个。从源码 src/utils/session.ts 可以印证这些机制过期判定读取createdAtmaxAge的基准与lastSeenAtidleTimeout的基准仅在设置idleTimeout时写入最终过期时间取createdAt maxAge与lastSeenAt idleTimeout的较早者。如果你来自express-session或koa-sessionidleTimeout就相当于它们的rolling选项。区别在于它自带独立时长而不是重新解释maxAge所以启用它不会牺牲绝对上限。重密封是会话中最昂贵的操作因此 H3 不会在每次请求时都做只有当窗口被用掉超过一半时才重密封且更新会话也算一次重密封。因此活跃用户永远不会被登出但记录的最近活动时间可能比真实时间最多落后半个窗口// idleTimeout: 60 * 30 // Sign-out happens 15 to 30 minutes after the last request, never later.如果你需要把该区间较短的一端作为真实上限可以把idleTimeout减半。相关实现可参见源码中基于SLIDE_THRESHOLD的shouldSlide判定见 src/utils/session.ts。[!NOTE] 只有 Cookie 会话才会滑动。通过x-{name}-session请求头传输的会话无法重密封因此它在密封签发后经过idleTimeout就会过期。[!IMPORTANT] 因为会话存放在 Cookie 中只读会话的请求在滑动窗口时也会把它写回。如果这样的请求与写入会话的请求并发浏览器最后应用哪个响应哪个就生效写入可能丢失。而不设置idleTimeout时只读请求不会设置任何 Cookie也就不会覆盖并发写入。[!NOTE] 滑动窗口的请求需要额外付出一次密封并在响应中携带Set-Cookie头——共享缓存和 CDN 通常拒绝存储这类响应。而只在节流窗口内读取会话的请求不会设置任何 Cookie。另外会话 Cookie 同样应用于错误响应因此抛出异常的请求仍会滑动窗口并在其中创建的新会话依然会被持久化。3.7 使用多个会话由于每个会话存放在各自的name下你可以对同一个请求运行多个相互独立的会话。它们位于不同的 Cookie 中、互不覆盖非常适合隔离不相关的关注点例如长生命周期的认证会话与短生命周期的 flash 消息import { useSession } from h3; app.use(async (event) { const auth await useSession(event, { name: auth, password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); const flash await useSession(event, { name: flash, password: 80d42cfb-1cd2-462c-8f17-e3237d9027e9, }); await flash.update({ message: Saved! }); // auth and flash are backed by different cookies, so they stay separate return { user: auth.data.user, flash: flash.data.message }; });[!NOTE] 请为每个会话指定不同的name。两个同名的会话共享同一个 Cookie后写者胜出。四、静态资源服务H3 可以服务 HTML、图片、CSS、JavaScript 等静态资源核心工具是serveStatic见 docs/4.examples/serve-static-assets.md 与源码 src/utils/static.ts。4.1 基本结构import { H3, serveStatic } from h3; const app new H3(); app.use(/public/**, (event) { return serveStatic(event, { getContents: (id) { // TODO }, getMeta: (id) { // TODO }, }); });仅凭这段代码还不会服务任何文件——你必须实现getContents与getMeta两个方法getContents读取文件内容。返回一个Promise解析为文件内容文件不存在时返回undefined。getMeta获取文件的元数据。返回一个Promise解析为元数据文件不存在时返回undefined。两者分离的设计目的是让 H3 可以在不读取文件内容的情况下响应HEAD请求并使用Last-Modified头做条件请求。4.2 结合node:fs的实现先在public目录中创建一个带简单消息的index.html然后在浏览器打开http://localhost:3000你就能看到这条消息。接着实现getContents与getMetaimport { stat, readFile } from node:fs/promises; import { join } from node:path; import { H3, serve, serveStatic } from h3; const app new H3(); app.use(/public/**, (event) { return serveStatic(event, { indexNames: [/index.html], getContents: (id) readFile(join(public, id)), getMeta: async (id) { const stats await stat(join(public, id)).catch(() {}); if (stats?.isFile()) { return { size: stats.size, mtime: stats.mtimeMs, }; } }, }); }); serve(app);getContents读取文件并返回其内容getMeta用fs.stat获取文件元数据若文件不存在或不是普通文件则返回undefined否则返回文件大小size与最后修改时间mtime。从源码看serveStatic的配置还支持etag、indexNames、编码协商等选项src/utils/static.tsid的解析与event.url.pathname保持一致默认会按[/index.html]查找目录索引文件当getMeta返回了etag时H3 会把它设置为响应头的etag随后通过条件请求匹配isCacheMatch判断是否返回304 Not Modified。文件大小与最后修改时间用于生成 etag如果文件自上次请求以来未发生变化H3 会返回304 Not Modified响应避免重复发送相同的文件——这对减少带宽消耗很有价值。五、流式响应Stream Response流式响应允许你在拿到数据的第一时间就发送给客户端非常适合大文件或长时间运行的响应见 docs/4.examples/stream-response.md。5.1 创建一个流先使用 Web 标准的ReadableStreamAPI 创建流const stream new ReadableStream();下面这个示例创建一个start函数每 100 毫秒发送一个随机数1000 毫秒后关闭流let interval: NodeJS.Timeout; const stream new ReadableStream({ start(controller) { controller.enqueue(ul); interval setInterval(() { controller.enqueue(li Math.random() /li); }, 100); setTimeout(() { clearInterval(interval); controller.close(); }, 1000); }, cancel() { clearInterval(interval); }, });5.2 发送流import { H3 } from h3; export const app new H3(); app.use((event) { // Set to response header to tell to the client that we are sending a stream. event.res.headers.set(Content-Type, text/html); event.res.headers.set(Cache-Control, no-cache); event.res.headers.set(Transfer-Encoding, chunked); let interval: NodeJS.Timeout; const stream new ReadableStream({ start(controller) { controller.enqueue(ul); interval setInterval(() { controller.enqueue(li Math.random() /li); }, 100); setTimeout(() { clearInterval(interval); controller.close(); }, 1000); }, cancel() { clearInterval(interval); }, }); return stream; });关键点有三处先设置三个响应头告知客户端正在接收流Content-Type: text/html、Cache-Control: no-cache、Transfer-Encoding: chunked用ReadableStream的start(controller)通过controller.enqueue(...)逐步推入数据用controller.close()结束并在cancel()中清理定时器避免客户端断开后继续泄漏资源直接把stream作为处理器返回值返回H3 会将其作为响应体发送。打开浏览器访问http://localhost:3000你会看到一个随机数列表每 100 毫秒新增一项。六、数据校验Validation服务端收到数据后必须先校验——所谓校验是指接收数据的形状必须符合预期形状。这一点至关重要因为你无法信任来自未知来源用户或外部 API的数据见 docs/4.examples/validate-data.md。[!WARNING] 不要把类型泛型当作校验。给readBody之类的工具提供一个 interface 并不是校验必须在使用数据之前真正执行校验。6.1 校验工具一览H3 提供三个校验工具getValidatedQuery—— 校验 querygetValidatedRouterParams—— 校验路由参数paramsreadValidatedBody—— 校验请求体body。H3 本身不捆绑任何校验库但支持来自Standard-Schema兼容库的 schema例如 Zod、Valibot、ArkType 等。若你想使用不兼容 Standard-Schema 的校验库仍然可以但必须使用该库自带的解析函数参见下文“安全解析”一节。[!WARNING] H3 是运行时无关的可在任意运行时中使用但部分校验库并非兼容所有运行时。6.2 校验路由参数用getValidatedRouterParams替代getRouterParams传入 schema 后直接得到校验结果import { getValidatedRouterParams } from h3; import * as z from zod; import * as v from valibot; // Example with Zod const contentSchema z.object({ topic: z.string().min(1), uuid: z.string().uuid(), }); // Example with Valibot const contentSchema v.object({ topic: v.pipe(v.string(), v.nonEmpty()), uuid: v.pipe(v.string(), v.uuid()), }); app.all( // You must use a router to use params /content/:topic/:uuid, async (event) { const params await getValidatedRouterParams(event, contentSchema); return You are looking for content with topic ${params.topic} and uuid ${params.uuid}.; }, );发送合法请求/content/posts/123e4567-e89b-12d3-a456-426614174000得到You are looking for content with topic posts and uuid 123e4567-e89b-12d3-a456-426614174000.如果校验失败H3 会抛出400 Validation Error错误数据中包含校验错误明细可在客户端用来向用户展示友好的错误信息。6.3 校验查询参数用getValidatedQuery替代getQuery。与上例不同的是这里还能借助校验库转换输入数据——例如把数字的字符串表示转换为真正的数字这对分页等场景非常有用import { getValidatedQuery } from h3; import * as z from zod; import * as v from valibot; // Example with Zod const stringToNumber z.string().regex(/^\d$/, Must be a number string).transform(Number); const paginationSchema z.object({ page: stringToNumber.optional().default(1), size: stringToNumber.optional().default(10), }); // Example with Valibot const stringToNumber v.pipe( v.string(), v.regex(/^\d$/, Must be a number string), v.transform(Number), ); const paginationSchema v.object({ page: v.optional(stringToNumber, 1), size: v.optional(stringToNumber, 10), }); app.use(async (event) { const query await getValidatedQuery(event, paginationSchema); return You are on page ${query.page} with ${query.size} items per page.; });发送合法请求/?page2size20得到You are on page 2 with 20 items per page.校验失败时同样抛出400 Validation Error错误数据中带有校验错误明细。6.4 校验请求体用readValidatedBody替代readBodyimport { readValidatedBody } from h3; import { z } from zod; import * as v from valibot; // Example with Zod const userSchema z.object({ name: z.string().min(3).max(20), age: z.number({ coerce: true }).positive().int(), }); // Example with Valibot const userSchema v.object({ name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)), age: v.pipe(v.number(), v.integer(), v.minValue(0)), }); app.use(async (event) { const body await readValidatedBody(event, userSchema); return Hello ${body.name}! You are ${body.age} years old.; });发送一个合法的 JSON POST 请求体{ name: John, age: 42 }得到Hello John! You are 42 years old.非法请求同样返回400 Validation Error及校验错误明细。6.5 安全解析Safe Parsing默认情况下直接向三个校验工具传入 schema作为第二个参数时校验失败会抛出400 Validation Error。但在某些场景下你可能想自己处理校验错误——这时需要把校验库的安全解析函数作为第二个参数传入。回到第一个示例Zod 的写法如下import { getValidatedRouterParams } from h3; import { z } from zod/v4; const contentSchema z.object({ topic: z.string().min(1), uuid: z.string().uuid(), }); app.all(/content/:topic/:uuid, async (event) { const params await getValidatedRouterParams(event, contentSchema.safeParse); if (!params.success) { // Handle validation errors return Validation failed:\n${z.prettifyError(params.error)}; } return You are looking for content with topic ${params.data.topic} and uuid ${params.data.uuid}.; });Valibot 的写法如下import { getValidatedRouterParams } from h3; import * as v from valibot; const contentSchema v.object({ topic: v.pipe(v.string(), v.nonEmpty()), uuid: v.pipe(v.string(), v.uuid()), }); app.all(/content/:topic/:uuid, async (event) { const params await getValidatedRouterParams(event, v.safeParser(contentSchema)); if (!params.success) { // Handle validation errors return Validation failed:\n${v.summarize(params.issues)}; } return You are looking for content with topic ${params.output.topic} and uuid ${params.output.uuid}.; });两种写法都接收{ success, ... }形式的结果对象失败时自行处理错误并返回友好的提示成功时从params.dataZod或params.outputValibot取出已解析的数据。同理getValidatedQuery与readValidatedBody也支持传入安全解析函数。结语从示例到生产回顾这六大示例可以看到 H3 的设计哲学核心框架保持极简通用能力以轻量工具函数的形式提供。Cookie 的三个工具函数覆盖了读写删全流程QUERY方法的一等公民支持让复杂只读查询有了标准答案Session 在useSession一个入口下隐藏了密封、滑动过期与多会话隔离等复杂机制serveStatic通过getContents/getMeta的职责分离天然支持HEAD与条件请求ReadableStream让流式响应无需任何额外抽象而基于 Standard-Schema 的校验工具则让你自由选择 Zod、Valibot 等生态。每个示例在仓库中都有对应的可运行文件与测试佐证。你可以在 examples/ 目录中找到cookies.mjs、query.mjs等完整实现直接运行相关单元测试如 test/unit/cookie-validation.test.ts、test/session.test.ts、test/static.test.ts、test/validate.test.ts则进一步验证了这些能力的行为边界。建议你结合本文逐步运行这些示例再对照源码理解其底层实现即可快速把这些实战能力应用到自己的 H3 服务中。【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表