中间件的本质:(c, next) => Promise<void> 的函数式组合
next() 调用前 = 请求阶段,next() 后 = 响应阶段
在 Hono 中,中间件(Middleware)并非一个复杂的抽象,而是回归其本质:一个接受上下文 c 和 next 函数的异步函数。其类型签名清晰地定义了其行为:
(c: Context, next: Next) => Promise<void>这一简洁的设计,使得中间件成为一种典型的函数式编程模式,支持组合、复用和分层,构成了 Hono 扩展能力的核心。
一、中间件的执行模型:洋葱模型(Onion Model)
Hono 的中间件采用经典的“洋葱模型”执行。当多个中间件被注册时,它们的执行流程如下:
外层中间件
│
▼
[请求阶段] ←─┐
│ │
▼ │
内层中间件 │
│ │
▼ │
[请求阶段] │
│ │
▼ │
处理器 │
│ │
▼ │
[响应阶段] ──┘
│
▼
内层中间件
│
▼
[响应阶段]
│
▼
外层中间件关键在于 next() 函数的调用时机:
next()调用前:属于“请求阶段”(Request Phase),你可以修改请求、读取头、验证权限等;next()调用后:属于“响应阶段”(Response Phase),你可以修改响应、记录日志、添加头等。
二、next() 的作用:控制执行流
next() 是一个函数,调用它表示“将控制权交给下一个中间件或最终处理器”。它的返回值是一个 Promise<void>,但更重要的是,你可以 await next(),从而在后续中间件和处理器执行完毕后,继续执行当前中间件的剩余逻辑。
示例:日志中间件
const logger = async (c: Context, next: Next) => {
const start = Date.now()
// 请求阶段:记录请求开始
console.log(`Request: ${c.req.method} ${c.req.url}`)
// 调用 next(),等待内层逻辑执行完毕
await next()
// 响应阶段:记录响应完成
const duration = Date.now() - start
console.log(`Response: ${c.res.status} in ${duration}ms`)
}在这个例子中:
await next()之前,是请求阶段;await next()之后,是响应阶段;c.res在next()调用后才存在,因为响应是由内层处理器生成的。
三、中间件的函数式特性
Hono 的中间件设计体现了函数式编程的核心思想:
高阶函数
中间件本身是一个函数,它接受c和next并返回Promise<void>。你也可以创建返回中间件的函数,实现参数化:tsconst auth = (role: string) => { return async (c: Context, next: Next) => { const userRole = c.req.header('X-User-Role') if (userRole !== role) { return c.json({ error: 'Forbidden' }, 403) } await next() } } app.use('/admin/*', auth('admin')) // 生成一个只允许 admin 的中间件组合性(Composition)
多个中间件可以像函数一样组合,形成处理管道:tsapp.use('/api/*', logger, auth('user'), cors())执行顺序为:
logger→ 请求阶段auth→ 请求阶段cors→ 请求阶段- 处理器
cors→ 响应阶段auth→ 响应阶段logger→ 响应阶段
无副作用或可控副作用
理想的中间件应尽量避免全局状态,而是通过c.set()和c.var在上下文中传递数据:tsconst userAgent = async (c: Context, next: Next) => { const ua = c.req.header('User-Agent') || 'Unknown' c.set('userAgent', ua) // 将数据注入上下文 await next() } app.use('/track', userAgent) app.get('/track', (c) => { const ua = c.var.userAgent // 安全获取 return c.json({ ua }) })
四、中断执行流:不调用 next()
中间件可以通过不调用 next() 来中断请求流程,直接返回响应。这是实现认证、限流、CORS 预检等逻辑的关键。
示例:CORS 预检处理
const cors = async (c: Context, next: Next) => {
if (c.req.method === 'OPTIONS') {
// 预检请求,直接返回成功,不调用 next()
c.res = new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
},
})
return
}
// 非预检请求,继续
await next()
}在此例中,OPTIONS 请求不会进入后续中间件或处理器,执行流在此中断。
五、错误处理中间件
Hono 支持错误处理中间件,其签名略有不同:
(c: Context, error: Error) => Response | Promise<Response>它在任意中间件或处理器抛出异常时被调用,用于统一返回错误响应:
app.onError((err, c) => {
console.error(err)
return c.json({ error: 'Internal Server Error' }, 500)
})六、结论:中间件是 Hono 的扩展基石
Hono 的中间件设计极简而强大:
- 类型
(c, next) => Promise<void>精确描述了其行为; next()的调用时机划分了请求与响应阶段;- 函数式组合使其易于复用和测试;
- 不调用
next()可中断流程,实现拦截逻辑。
正是这种设计,使得 Hono 能够通过极小的核心,支持认证、日志、压缩、验证、CORS 等丰富功能,真正实现“微框架,大生态”。