H3 更多工具
这一节收的是不好归类的一批:前缀剥离、事件判定与构造、四个中间件工厂、以及 Node/Web 适配器。数量不多,但每一个都在「把 H3 接到别的生态」时用得上。
withBase:剥掉前缀再交给子 handler
import { H3, withBase } from "h3";
// 一个只认识自己根路径的子应用
const api = new H3().get("/", () => "Hello API!");
// 挂到 /api 下:收到 /api/* 时先把前缀剥掉,再交给子应用
const app = new H3().use("/api/**", withBase("/api", api.handler));
它和 mount 的区别要分清,否则会写出「路由注册了但不匹配」的代码:
| 方式 | 机制 | 路由表 | 适合 |
|---|---|---|---|
mount("/api", subApp) |
注册期把子应用路由逐条搬进主应用,前缀拼好 | 合并 | 想让主应用也能看到子应用的路由 |
withBase("/api", subApp.handler) |
运行期剥前缀后转发 | 不合并 | 只想把请求转过去,保持两套路由独立 |
withBase 也能用于第三方 handler——只要它符合 Request => Response。
事件判定与构造
| 函数 | 作用 |
|---|---|
isEvent(input) |
判断是不是 H3Event 实例 |
isHTTPEvent(input) |
判断是不是「长得像事件」的对象(有 req: Request) |
getEventContext(event) |
取 context;不存在就在 req.context 上初始化一个 |
mockEvent(request, options?) |
造一个假事件,测试用 |
isHTTPEvent 比 isEvent 宽松,写通用工具函数时用它更合适——调用方可能传的是别家框架的事件,只要有 req 就能用。
测试里造事件:
import { mockEvent, getCookie } from "h3";
// 不需要启动服务器,直接构造事件
const event = mockEvent(new Request("http://localhost/", {
headers: { cookie: "token=abc" },
}));
const token = getCookie(event, "token"); // "abc"
注意 mockEvent 造出的事件同样会走路径归一化(4.2 讲过),所以测试里的路径处理与线上一致——这点在写安全相关测试时很重要。
四个中间件工厂
这几个函数容易被误解:它们返回的是中间件,不是全局钩子。
import { H3, onRequest, onResponse, onError, bodyLimit } from "h3";
const app = new H3()
// 作为中间件挂在路由上,只对这一组路由生效
.use("/api/**", onRequest((event) => {
event.context.startedAt = Date.now();
}))
// 中间件版 onResponse 可以返回新 Response 来替换原响应
.use("/api/**", onResponse((response, event) => {
const copy = new Response(response.body, response);
copy.headers.set("x-elapsed", String(Date.now() - Number(event.context.startedAt)));
return copy;
}))
// 自定义错误响应
.use("/api/**", onError((error) => Response.json({ message: error.message }, { status: 500 })))
// 限制请求体大小,超限在读取时抛 413
.use("/api/**", bodyLimit(1024 * 1024));
与 4.1 的全局钩子有一处关键差异:中间件版 onResponse 可以 return 一个新 Response 替换原响应;全局钩子 onResponse 的返回值会被忽略,只做观测。所以「要改响应」用中间件版,「要埋点」用全局钩子。
bodyLimit 的执行方式和 assertBodySize 一样是边读边计数,所以只有 handler 真的读了 body 才会触发 413。要在入口处就挡住超大请求,还得配合 Content-Length 检查或显式读取。
WebSocket 相关的两个
defineWebSocket(hooks) 只是给 hooks 对象加类型,行为上等于原样返回;defineWebSocketHandler(hooks, http?) 才真正注册路由。完整用法(crossws 依赖、426 默认行为、hooks 挂在请求上)见 5.2。
四个适配器
| 函数 | 方向 | 用途 |
|---|---|---|
defineNodeHandler(handler) |
Node → H3 | 把 Node 风格 handler 包成 H3 handler(带类型推导) |
defineNodeMiddleware(middleware) |
Node → H3 | 把 (req, res, next) 中间件包成 H3 中间件 |
fromNodeHandler(handler) |
Node → H3 | 同上,运行时转换(不要求类型) |
fromWebHandler(handler) |
Web → H3 | 把标准 Request => Response 的 handler 接进来 |
v1 里这几个名字不一样:toNodeListener 现在叫 toNodeHandler,fromNodeMiddleware 叫 fromNodeHandler,defineNodeListener 叫 defineNodeHandler。完整的改名对照在 8.1。
import { fromWebHandler, defineNodeMiddleware } from "h3";
import express from "express";
// 把 fetch 风格的第三方 handler 接进 H3
app.use("/legacy", fromWebHandler(otherFetchHandler));
// 把 Express 中间件接进来:老项目迁移时最常用
app.use(defineNodeMiddleware(express.json()));
排查表
| 现象 | 最可能原因 | 处理 |
|---|---|---|
用 withBase 后子应用 404 |
前缀与实际挂载路径不一致 | 两处字符串保持相同 |
onResponse 中间件改了响应没生效 |
改了 response.headers 但没 return 新对象 |
构造新 Response 并返回 |
| 全局钩子改响应无效 | 全局钩子的返回值被忽略 | 改用中间件版 |
bodyLimit 没拦住大请求 |
handler 没读 body | 显式读取或检查 Content-Length |
| 接入 Express 中间件后 body 代理失败 | 中间件读了 body | 换 clone() 读,或调整中间件顺序 |
| 测试里路径判断与线上不一致 | 手写了 event 而没用 mockEvent |
用 mockEvent 构造 |
延伸阅读
- 全局钩子与中间件的差别:见 4.1 应用实例 API
- 挂载与嵌套应用:见 2.7 嵌套应用
- WebSocket 的完整接入流程:见 5.2 WebSocket 与 SSE
- v1 到 v2 的改名清单:见 8.1 v1 到 v2 迁移:核心变更

TRAE-AI编程


更多建议: