常用中间件深度解析
Hono 的轻量核心通过中间件实现丰富的功能。以下是对 logger、cors 和 jwt 三类常用中间件的深入剖析,揭示其内部机制与最佳实践。
一、logger:如何记录请求耗时与状态码?
目标:在请求开始和响应结束后记录日志,包含方法、路径、状态码和处理时间。
核心机制:利用洋葱模型的“前后阶段”
ts
const logger = async (c, next) => {
const start = Date.now()
const { method, path } = c.req
// 请求阶段:记录请求开始
console.log(`→ ${method} ${path}`)
// 等待内层逻辑执行(可能是其他中间件或处理器)
await next()
// 响应阶段:此时 c.res 已被设置
const duration = Date.now() - start
const status = c.res?.status || 500
console.log(`← ${method} ${path} ${status} in ${duration}ms`)
}关键点:
await next()是分水岭:调用前是请求阶段,调用后是响应阶段;c.res在next()后才存在:因为响应是由内层处理器生成的;- 性能:使用
Date.now()而非new Date(),避免对象创建开销; - 生产优化:可结合环境变量控制日志级别。
增强版:结构化日志
ts
const structuredLogger = (options = {}) => {
return async (c, next) => {
const start = performance.now()
await next()
const duration = performance.now() - start
const log = {
time: new Date().toISOString(),
method: c.req.method,
url: c.req.url,
status: c.res.status,
duration_ms: duration.toFixed(2),
userAgent: c.req.header('User-Agent'),
ip: c.req.header('X-Forwarded-For') || 'unknown'
}
console.log(JSON.stringify(log))
}
}二、cors:预检请求(OPTIONS)如何自动处理?
目标:支持跨域请求,自动响应浏览器的预检(Preflight)请求。
浏览器 CORS 预检机制
当请求满足以下条件之一时,浏览器会先发送 OPTIONS 请求:
- 非简单请求(如
Content-Type: application/json) - 自定义头
- 使用
PUT、DELETE等方法
服务器必须正确响应 OPTIONS 请求,否则实际请求不会发出。
cors 中间件工作流程
ts
const cors = (options = {}) => {
const {
origin = '*',
allowMethods = ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders = ['Content-Type', 'Authorization']
} = options
return async (c, next) => {
// 设置通用 CORS 头
c.res.headers.set('Access-Control-Allow-Origin', origin)
c.res.headers.set('Access-Control-Allow-Methods', allowMethods.join(','))
c.res.headers.set('Access-Control-Allow-Headers', allowHeaders.join(','))
// 检查是否为预检请求
if (c.req.method === 'OPTIONS') {
// 直接返回 204 No Content,不调用 next()
return new Response(null, {
status: 204,
headers: c.res.headers // 携带已设置的 CORS 头
})
}
// 非预检请求,继续执行后续逻辑
await next()
}
}关键点:
- 短路机制:
OPTIONS请求直接返回,不进入业务逻辑; - 头信息注入:CORS 头在
next()调用前设置,确保最终响应包含这些头; - 灵活性:支持自定义
origin、allowMethods等选项; - 安全性:生产环境避免使用
*作为origin,应指定具体域名。
使用示例:
ts
app.use('/api/*', cors({
origin: 'https://myapp.com',
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'X-API-Key']
}))三、jwt:解析 Token、注入 c.var 的安全上下文
目标:验证 JWT Token,提取用户信息,并安全地注入请求上下文。
实现原理
ts
import { verify } from 'hono/jwt'
const jwtAuth = (options) => {
const { secret, cookieName = 'token' } = options
return async (c, next) => {
// 1. 从请求中提取 Token
const authHeader = c.req.header('Authorization')
const token = authHeader?.split(' ')[1] // Bearer <token>
|| c.req.cookie(cookieName)
if (!token) {
return c.json({ error: 'Token required' }, 401)
}
try {
// 2. 验证并解码 Token
const payload = await verify(token, secret)
// 3. 将用户信息注入上下文
c.set('user', payload)
c.set('userId', payload.sub)
// 4. 继续执行
await next()
} catch (err) {
return c.json({ error: 'Invalid or expired token' }, 401)
}
}
}关键点:
- 多源 Token 支持:从
Authorization头或 Cookie 读取; c.set()安全注入:c.var是类型安全的上下文存储,用于传递数据给下游处理器;- 错误处理:JWT 验证失败(过期、签名无效)应返回
401; - 性能:
verify是异步操作,需await;考虑缓存高频验证结果。
下游处理器使用:
ts
app.get('/profile', jwtAuth({ secret: 'my-secret' }), (c) => {
const user = c.var.user // 类型安全获取
return c.json({ profile: user })
})安全建议:
- 使用强密钥(
process.env.JWT_SECRET); - 设置合理的过期时间(
exp); - HTTPS 传输;
- 对敏感操作进行二次验证。
四、总结
| 中间件 | 核心机制 | 关键技术 |
|---|---|---|
logger | 洋葱模型 + 时间差 | await next() 分隔请求/响应阶段 |
cors | 短路 + 头注入 | 拦截 OPTIONS,设置 CORS 头 |
jwt | 认证 + 上下文注入 | verify + c.set() |
这三类中间件代表了 Hono 扩展能力的典型模式:
- 观察型(logger):不改变流程,仅记录;
- 拦截型(cors, jwt):条件性短路,保护资源;
- 增强型(jwt):注入数据,丰富上下文。
理解其内部机制,有助于你编写更高效、更安全的自定义中间件。