ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

H3 应用实例完全指南:从 `new H3()` 到请求分发与全局钩子

2026/9/17 10:10:59 拓冰建站 浏览量
H3 应用实例完全指南:从 `new H3()` 到请求分发与全局钩子 H3 应用实例完全指南从new H3()到请求分发与全局钩子【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3本文以 H3 框架的H3应用类为核心系统讲解如何创建应用实例、注册路由与中间件、挂载子应用、配置全局选项与生命周期钩子并深入源码剖析其内部的路由匹配、中间件组合与 URL 路径规范化机制。读完本文你将掌握H3类的全部 API 用法并理解每个配置项背后的实现原理能够独立构建一个多路由、带中间件与嵌套应用的高性能 HTTP 服务。概览H3类是服务器的核心H3类是 H3 服务器一切能力的入口。在 src/h3.ts 中H3继承自H3Core后者负责事件分发、中间件组合与响应生成等底层逻辑H3则在之上叠加了基于 Rou3 的 HTTP 路由匹配能力~rou3路由器与面向开发者的链式 API。创建一个应用实例只需要一行代码import { H3 } from h3; const app new H3({/* optional config */});构造函数接收一个可选的全局配置对象详见下文 H3 Options。从源码看H3的构造函数除了初始化路由器和绑定request方法外还会立即执行config.plugins中注册的插件见 src/h3.ts。H3Methods从请求入口到路由注册H3.requestH3.request是一个 fetch 兼容的函数用于直接向应用发起请求、获取响应非常适合做内部调用、测试或服务端渲染场景。输入可以是相对路径、URL 或 Request 对象返回值为Response的 Promise。const response await app.request(/); console.log(response, await response.text());从实现看request()方法 会先调用toRequest将输入统一转换为标准的 WebRequest再交给内部~request处理。当传入相对路径时toRequest会基于请求的host头合成完整 URL缺失或非法时回退到localhost并且永远使用http协议、忽略x-forwarded-proto这是刻意为之的安全设计见 src/utils/request.ts。request还支持可选的RequestInit与上下文参数const response await app.request(/api, { method: POST, headers: { content-type: application/json }, body: JSON.stringify({ name: h3 }), });H3.fetchH3.fetch与H3.request类似但只接受一个(req: Request)参数以换取跨运行时的一致性。它是各平台serve(app)实际使用的底层入口在 src/_entries/node.ts 等入口文件中serve会把app.fetch直接作为 fetch handler 交给 srvx 服务器export function serve(app: H3, options?: OmitServerOptions, fetch): Server { freezeApp(app); return srvxServe({ fetch: app.fetch, ...options }); }因此H3.fetch才是真正被运行时调用的分发入口而H3.request更偏向开发与测试用途。H3.onH3.on为指定 HTTP 方法注册路由处理器const app new H3().on(GET, /, () OK);方法名不区分大小写——源码中on()会将方法统一toUpperCase()后交给 Rou3 路由器见 src/h3.ts。它支持的可选第三个参数RouteOptions包含middleware仅该路由生效的中间件数组与meta路由元数据可用于给路由附加权限标记等自定义信息。H3.[method]H3.[method]是app.on(method, ...)的快捷写法H3 内置了GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、CONNECT、TRACE、QUERY共 10 个方法的快捷注册const app new H3().get(/, () OK);这些快捷方法是在 src/h3.ts 中通过循环批量生成在原型上的for (const method of [GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT, TRACE, QUERY] as const) { (H3Core as any).prototype[method.toLowerCase()] function (route, handler, opts) { return this.on(method, route, handler, opts); }; }注意两点所有注册方法都返回this因此可以无限链式调用QUERY是 RFC 10008中的说明。H3.allH3.all为所有 HTTP 方法注册路由处理器内部实现为this.on(, route, handler, opts)空方法名即通配const app new H3().all(/, () OK);典型用法是作为兜底路由fallback与get/post等精确方法路由共存app .get(/hello, () GET Hello world!) .post(/hello, () POST Hello world!) .all(/hello, () Any other method!);H3.useH3.use注册全局中间件每次请求都会先经过它们再进入路由处理器const app new H3() .use((event) { console.log(request: ${event.req.url}); }) .all(/, () OK);use的签名非常灵活支持三种调用形态见 src/h3.ts// 1. 仅函数作用于所有请求 app.use((event) { /* ... */ }); // 2. 带路径与选项 app.use(/blog/**, handler, { method: POST, match: (event) /* ... */ }); // 3. 传入另一个 H3 实例 —— 自动转为 mount 挂载 app.use(nestedApp);第三种形态很关键当use收到的参数带有handler属性即是一个 H3 实例时会自动委托给mount完成挂载。MiddlewareOptions支持method与match(event)两个过滤选项。中间件的完整用法含next拦截、返回值短路行为参见中间件指南。H3.registerH3.register用于注册一个 H3 插件来扩展应用import { definePlugin } from h3; const logger definePlugin((h3, _options) { h3.use((event) { console.log([${event.req.method}] ${event.url.pathname}); }); }); app.register(logger());插件本质是接收 H3 实例并立即执行的函数也可在构造时通过plugins配置项传入。注意插件总是立即注册因此注册顺序可能影响行为。详见插件指南。H3.handlerH3.handler是一个 H3 事件处理器用于组合多个 H3 应用实例。它接受一个H3Event内部完成路由查找并把params、matchedRoute写入event.context与分发见 src/h3.tshandler(event: H3Event): unknown | Promiseunknown { const route this~findRoute; if (route) { event.context.params route.params; event.context.matchedRoute route.data; } return (this[~dispatch] ?? createDispatcher(this))(event, route); }示例嵌套应用。把子应用的handler通过withBase挂到主应用的通配路由上import { H3, serve, redirect, withBase } from h3; const nestedApp new H3().get(/test, () /test (sub app)); const app new H3() .get(/, (event) redirect(event, /api/test)) .all(/api/**, withBase(/api, nestedApp.handler)); serve(app);当请求/api/test时withBase会在调用nestedApp.handler前剥离/api前缀使子应用在自己的命名空间内完成匹配。H3.mountH3.mount是更直接的子应用注册方式——以指定前缀挂载一个子应用const nestedApp new H3().get(/test, () /test (sub app)); const app new H3().mount(/api, nestedApp);mount支持两类目标见 src/h3.tsH3 实例子应用的所有路由都会带上前缀合并进主应用子应用的全局中间件会被包装成一条带前缀守卫的中间件不匹配前缀时直接next()匹配时才剥离前缀并执行且会处理/base//evil.com这类可能被下游重定向利用的协议相对 URL 攻击面任意.fetch兼容的 Web 标准应用如 Hono、Elysia通过app.all(base /**, ...)通配转发并把基础前缀从传给子应用的request.url中移除。子应用的全局配置与钩子不会被继承请一律在主应用中设置。更多细节见嵌套应用指南。H3Options全局配置创建实例时可传入全局应用配置。以 src/types/h3.ts 的类型定义为准支持以下选项选项类型默认行为说明debugboolean关闭在 HTTP 错误响应中输出调试用堆栈跟踪生产环境有安全风险silentboolean关闭开启后未处理异常的控制台错误将不再打印allowMalformedURLboolean关闭允许畸形百分号编码的 URL 路径如/foo%、/%ZZ以原始路径名通过而不是默认的400 Bad Request拒绝pluginsH3Plugin[]空构造时立即注册的插件数组const app new H3({ debug: false, silent: false, allowMalformedURL: false, plugins: [logger()], });[!IMPORTANT] 开启debug选项会在错误响应中泄露堆栈跟踪等重要信息仅应在开发环境启用。allowMalformedURL的底层实现值得展开在 src/h3.ts 的~request方法中H3 会在任何路由匹配或应用逻辑执行之前检查事件是否携带畸形 URL 标记若是则直接抛出400 Bad Requestif ((event as any)[kMalformedURL] !this.config.allowMalformedURL) { throw new HTTPError({ status: 400, message: Bad Request }); }该标记由H3Event构造函数在解析 URL 时打上见 src/event.ts当路径包含%且无法通过decodeURI解码如/foo%、/%ZZ、/%80这类截断/非十六进制/非法 UTF-8 转义时置位。畸形路径没有可解码的规范形式因此被提前拒绝。全局钩子Global Hooks初始化应用时可以注册三个全局钩子它们会在每个请求的生命周期中被调用onError—— 请求处理抛出错误时触发onRequest—— 请求分发前触发onResponse—— 响应生成后触发。const app new H3({ onRequest: (event) { console.log(Request:, event.req.url); }, onResponse: (response, event) { console.log(Response:, event.url.pathname, response.status); }, onError: (error, event) { console.error(error); }, });从 src/h3.ts 的源码可以看到onRequest的执行时机——它在H3Event创建之后、handler()分发之前被调用且支持返回 Promise 以异步化if (this.config.onRequest) { const hookRes this.config.onRequest(event); handlerRes typeof hookRes?.then function ? hookRes.then(() this.handler(event)) : this.handler(event); } else { handlerRes this.handler(event); }而onResponse与onError则由toResponse在响应准备阶段调用toResponse(handlerRes, event, this.config)见 src/h3.ts。[!IMPORTANT] 全局钩子只在主 H3 应用中运行子应用sub-app不会继承。需要更灵活的逻辑时请使用中间件。全局钩子与生命周期理解这几个钩子与中间件、路由处理器的先后关系可以帮助你决定逻辑该放哪。H3 的完整请求生命周期详见生命周期指南如下接收请求运行时调用app.fetch(request)创建事件new H3Event(request)初始化事件含 URL 规范化随后调用onRequest(event)钩子再进入app.handler(event)分发请求基于request.url与request.method匹配路由依次执行全局中间件、路由级中间件最后执行命中的路由处理器发送响应将返回值与准备好的响应头转换为Response调用onResponse(response, event)钩子返回给运行时。H3Properties读取应用配置H3.configH3.config暴露当前 H3 实例的全局配置对象即构造时传入的选项见 src/h3.ts 的readonly config。它在插件与中间件中尤为常用——例如插件可以根据h3.config.debug决定是否启用日志参考插件指南中的definePlugin示例。const app new H3({ debug: true }); // 在插件或中间件中读取 app.use((event) { if (event.app?.config.debug) { console.log([${event.req.method}] ${event.url.pathname}); } });源码纵深H3 的内部分发机制理解了全部公开 API 后我们来看 H3 是如何把注册变成分发的这能帮助你更好地预测复杂场景下的行为。1. 路由匹配与 HEAD 回退H3使用 Rou3 作为路由匹配引擎。路由注册时~addRoute会把方法 规范化后的路由模式加入 Rou3 树见 src/h3.ts。匹配时~findRoute见 src/h3.ts如果当前方法未命中且方法是HEAD会自动回退查找同路径的GET路由遵循 RFC 9110 的 HEAD 语义同时保留HEAD方法名以便响应阶段剥离 bodyif (match undefined _event.req.method HEAD) { return findRoute(this[~rou3], GET, _event.url.pathname); }因此你无需为 HEAD 单独注册处理器app.get(/hello)即可同时响应HEAD /hello返回相同状态码与响应头但 body 为空。显式注册的app.head()路由永远优先于自动回退。2. 中间件预组合precomposition与缓存失效为了性能H3 会对中间件链做预组合首次请求时或use()/mount()使缓存失效后把全局中间件一次性composeMiddleware成一个函数并缓存在~composed上之后每个请求直接调用组合结果不再逐条遍历见 src/h3.ts。同理路由级中间件 处理器也会在首次命中时组合并缓存在路由对象上~composed见routeHandler。两个值得注意的细节若子类或实例覆写了~getMiddleware例如 Nitro 这类基于 H3 的上层框架预组合会退化为逐请求动态组合兼容路径use()和mount()都会把~dispatch/~composed置为undefined以强制重新组合。3. URL 路径规范化为何/%61dmin无法绕过/adminH3Event构造函数在创建事件时src/event.ts会对 URL 路径做一次规范化处理所有无谓转义解码后字符在 WHATWG 路径序列化中原样保留且不是%2f/%5c/%25的转义都会被解码。这是为了消除下游解码消费者看到的路径与H3 匹配器看到的路径之间的差异——否则/%61dmin可以绕过/admin守卫却仍到达/admin路由。具体的规范化规则与表格见 H3Event 文档 的 Pathname encoding 一节核心结论请求event.url.pathname原因/%61dmin/admin无谓转义未保留字符解码/a%2eb/a.b无谓转义解码/x%2fy/x%2fy分隔符必须保持编码/100%25/100%25解码会暴露嵌套转义/a%20b/a%20bURL 序列化器本就会重新编码空格需要强调的安全准则永远不要自己解码event.url.pathname——解码可能重新引入路由和中间件从未见过的/或..一旦该值进入文件系统或上游 URL 就是路径穿越向量。读取解码后的路由参数请使用getRouterParams(event, { decode: true })做作用域检查请使用resolveDotSegments。而畸形编码如/foo%没有可解码的规范形式默认在任何处理器运行前以400 Bad Request拒绝——除非你显式设置allowMalformedURL。4. 响应准备~request最后把处理器返回值交给toResponse它会根据返回值类型字符串、对象、Response、ReadableStream等生成最终Response应用event.res中准备好的状态码与响应头详见响应指南并调用onResponse钩子。任何同步抛错或异步 reject 都会被捕获并转换为HTTPError响应在debug开启时附带堆栈。实战组合示例最后把本文的核心 API 组合成一个完整应用覆盖路由注册、中间件、钩子、嵌套挂载与请求自测import { H3, serve } from h3; // 子应用独立的路由与中间件 const api new H3() .use((event) { event.res.headers.set(x-api, 1); }) .get(/test, () /api/test (sub app)); // 主应用 const app new H3({ debug: process.env.NODE_ENV development, onRequest: (event) { console.log(Request: ${event.req.method} ${event.url.pathname}); }, onResponse: (response, event) { console.log(Response: ${event.url.pathname} → ${response.status}); }, }) .use((event) { console.log([middleware] ${event.req.url}); }) .get(/, () OK) .post(/, async (event) { const body await event.req.json(); return { received: body }; }) .mount(/api, api); // 内部自测无需启动服务器即可验证路由 const res await app.request(/api/test); console.log(await res.text()); // /api/test (sub app) // 启动服务器Node / Bun / Deno / Cloudflare 等各平台入口一致 serve(app);H3类是理解整个框架的钥匙方法层on/get/all/use/mount/register负责声明式地组装应用选项层debug/silent/allowMalformedURL/plugins/全局钩子控制分发行为与错误处理策略而H3Core的分发管线则保证了中间件预组合、HEAD 回退与路径规范化这些底层语义的一致性。掌握这些你就掌握了 H3 从请求到响应的全部脉络。【免费下载链接】h3⚡️ Minimal H(TTP) framework built for high performance and portability项目地址: https://gitcode.com/GitHub_Trending/h31/h3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考