Skip to content

常用中间件深度解析

Hono 的轻量核心通过中间件实现丰富的功能。以下是对 loggercorsjwt 三类常用中间件的深入剖析,揭示其内部机制与最佳实践。


一、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.resnext() 后才存在:因为响应是由内层处理器生成的;
  • 性能:使用 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
  • 自定义头
  • 使用 PUTDELETE 等方法

服务器必须正确响应 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() 调用前设置,确保最终响应包含这些头;
  • 灵活性:支持自定义 originallowMethods 等选项;
  • 安全性:生产环境避免使用 * 作为 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):注入数据,丰富上下文。

理解其内部机制,有助于你编写更高效、更安全的自定义中间件。