H3 工具函数实战指南6 个场景下如何选对 H3 utils【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3H3 是一个极简的 HTTP 框架但它自带的 H3 工具函数utils一点不少H3 请求体解析、Cookie 操作、安全中间件、反向代理、JSON-RPC 全被拆成了独立的小函数。这篇文章按 6 个真实开发场景带你看清这套 H3 utils——每个工具解决什么问题、关键怎么用、以及背后的设计取舍。从请求生命周期看 H3 utils 工具函数的全貌H3 的核心只做三件事接事件、做路由、编排中间件。其余一切能力——解析 body、构造响应、发 Cookie、校验安全边界、代理请求——都不在核心里而是拆成独立工具函数统一从 src/index.ts 的 Utils 段导出按 Request、Response、Cookie、Proxy、Session、CORS、Auth、WebSocket、JSON-RPC 等二十多个类别组织。 打个比方H3 核心像一间只留了灶台和盘子的厨房工具函数就是按需取用的刀具锅具用不到的不进你的代码路径应用保持轻量而且内置工具和自研工具遵循同一套(event) ...签名可以无缝混用。下面按场景看。H3 请求体解析为什么必须显式开启最常见的坑handler 里随手一句readBody(event)结果一个恶意客户端把Content-Type写成multipart/form-data服务端就被拖去做昂贵的 multipart 解析资源被一个不可信请求吃光。所以 H3 的解析策略严格 opt-inreadBody默认按 JSON 解析application/x-www-form-urlencoded时回退为 URL 编码multipart 等类型必须显式传options.type: formData绝不从请求头自动探测。因为 Content-Type 是客户端可控输入自动探测等于把解析成本的控制权交给对方src/utils/body.ts 注释里点名的 #875 就是这个教训。读出来想校验交给readValidatedBody(event, validate)校验器可以是普通函数也可以是 Standard-Schemazod、valibot 兼容的校验库通用接口约定库onError可自定义失败响应。大小控制用assertBodySize(event, limit)它不预缓冲而是把请求包成边读边计数的代理累计字节超限立刻以413中断谎报的小Content-Length也逃不掉诚实的超大Content-Length在处理前直接413同时携带Content-Length和Transfer-Encoding则判定为请求走私RFC 7230返回400。中间件版本是bodyLimit(limit)语义相同。app.post(/, async (event) { assertBodySize(event, 10 * 1024 * 1024); // 10MB const body await readBody(event); });查询参数侧是getQuery(event)与getValidatedQuery(event, validate)套路和 body 一致不再重复。为什么 H3 默认不信任转发头和 HostgetRequestHost/getRequestURL/getRequestProtocol有个共同的反直觉默认都不读x-forwarded-*头。因为这类头是客户端可以直接写入的输入默认信任它任何调用者都能伪装自己的主机与协议。只有确认应用跑在会覆写这些头的可信代理或 CDN 后面才显式开启xForwardedHost: true。更隐蔽的坑在 Host由Host头推导的 origin 绝不能用于 CSRF/origin 校验、缓存键或生成发给其他用户的绝对链接——攻击者可以构造任意 Host让校验通过的 origin 变成他的站点或让邮件里的链接指向他的域名除非上游对 Host 做了白名单固定。IP 同理getRequestIP默认取event.req.ip连接对端或服务器信任代理后解析出的地址xForwardedFor: true改取x-forwarded-for链的第一个条目而那正是客户端能自己写的值——等于允许任何人自报 IP白名单、限流、审计全部形同虚设。更稳的做法是让服务器如 srvx 的trustProxy从右往左解析可信代理链。方法校验用isMethod/assertMethod后者不匹配时抛带Allow头的405RFC 9110。手上是裸Request时toRequest(input, options)/requestWithBaseURL(req, base)负责规范化相对 URL 基于Host头合成协议恒为httpHost 只作 authority——想控制 origin 就传绝对 URLsrc/utils/request.ts。路由参数为什么只解码一次看一个真实攻击面路由/files/**:rest请求GET /files/%252e%252e/x。getRouterParams(event)默认返回 URL 里的原始编码形态decode: true只解码一层得到%2e%2e/x。这里有两个刻意为之的限制路径分隔符%2f、%5c及任意%25嵌套永远不解码因为按单段匹配出来的参数不能悄悄长出路由和中间件从未见过的/且解码一层后结果仍可能含%XX%252e→%2e。 红线是返回值不要再解码。第二次decodeURIComponent会把%2e%2e/x还原成../x、把%00还原成 NUL 字节——这些穿越和控制字符对路由和基于路径名的中间件全程不可见是标准的路径穿越二次解码漏洞。正确姿势是交给 src/utils/path.ts 的resolveDotSegments(path)它解析./..段且永不逃逸到根/之上结果恒为单一前导/杜绝//host协议相对形态还会在任意%25深度解码编码的点段、把\规范成/所以编码式和反斜杠式穿越与字面../一视同仁。配套的isCanonicalPath(path)是解析器自身的快速路径守卫让热路径上的 scope 检查能跳过规范化文档特意强调scope 检查漏掉一次规范化是绕过漏洞不是性能问题。app.get(/files/**:rest, (event) { const rest getRouterParams(event, { decode: true }).rest; // %2e%2e/x const safe resolveDotSegments(/files/ rest); return safe; // 用作文件路径勿再 decodeURIComponent(rest) });流式输出、重定向白名单和响应清理时机iterable(iterable)按顺序逐块发送生成器里可以穿插await。关键时序首个 chunk 在响应创建前就被 await所以只有第一块之前设置的status和 headers 生效之后再改头就是徒劳——头已上线。配套html/raw模板标签html自动转义插值raw把字符串标记为已预转义直接输出用户输入绝不进raw否则 XSS 原路返回。重定向有两个坑。redirect(location, status)默认 302 并附 meta refresh 兜底页若location来自用户输入而不做白名单校验就是开放重定向——用户被诱导向钓鱼站跳转而跳转链上还挂着你的域名背书。redirectBack(event, { fallback })基于referer回上一页默认只取 pathnamereferer 缺失或跨源落到fallback它必须是硬编码的可信路径。清理时机是另一个易混点onDispose(event, cb)表示h3 处理完这个事件流式传输完成、客户端断开或出错回调在全局onResponse钩子之后按注册顺序执行而要在还在产出响应时响应客户端断开比如中止进行中的上游 fetch该用event.req.signal两者分工不同。另外还有noContent(status)、writeEarlyHints和状态码/描述文本的两个 sanitize 函数src/utils/response.ts。app.get(/log, (event) { const timer setInterval(rotateLog, 1000); onDispose(event, () clearInterval(timer)); return iterable(logStream); // 流式响应 });把 Cookie、会话和 CORS 拼【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考