H3 更多工具

2026-10-30 17:48 更新

这一节收的是不好归类的一批:前缀剥离、事件判定与构造、四个中间件工厂、以及 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 迁移:核心变更
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号