Chapter 04

中间件体系

洋葱模型、内置中间件生态与自定义中间件工厂——构建可复用的请求处理管道

中间件原理:洋葱模型

Hono 的中间件采用"洋葱模型"(Onion Model)——请求像剥洋葱一样从外层向内层依次执行,响应再从内层向外层依次执行。每个中间件都可以在 next() 前后执行逻辑:

// 中间件结构:async (c, next) => { ... await next() ... }
app.use(async (c, next) => {
  console.log('[中间件A] 进入')
  await next()           // 调用下一个中间件/处理函数
  console.log('[中间件A] 返回')  // 响应返回后执行
})

app.use(async (c, next) => {
  console.log('[中间件B] 进入')
  await next()
  console.log('[中间件B] 返回')
})

app.get('/', (c) => {
  console.log('[处理函数] 执行')
  return c.text('Hello')
})

// 请求 GET / 的执行顺序:
// [中间件A] 进入
// [中间件B] 进入
// [处理函数] 执行
// [中间件B] 返回
// [中间件A] 返回

洋葱模型的强大之处:中间件可以在请求进入时预处理(鉴权、日志开始),也可以在响应离开时后处理(添加响应头、记录耗时、压缩响应体)。这种双向处理能力是其他"单向"中间件模型(如 Express 的 next 链)所不具备的。

内置中间件

Logger — 请求日志

import { Hono } from 'hono'
import { logger } from 'hono/logger'

const app = new Hono()

app.use(logger())  // 全局日志

// 输出格式:
// --> GET /api/users
// <-- GET /api/users 200 12ms

CORS — 跨域资源共享

import { cors } from 'hono/cors'

// 简单配置(允许所有来源,不推荐用于生产)
app.use(cors())

// 完整配置
app.use(cors({
  origin: ['https://myapp.com', 'https://staging.myapp.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowHeaders: ['Content-Type', 'Authorization'],
  exposeHeaders: ['X-Total-Count'],
  credentials: true,  // 允许携带 Cookie
  maxAge: 3600,       // OPTIONS 预检缓存 1 小时
}))

// 仅对 /api/* 路径启用 CORS
app.use('/api/*', cors({ origin: '*' }))

Compress — 响应压缩

import { compress } from 'hono/compress'

// 自动根据 Accept-Encoding 选择 gzip 或 deflate
app.use(compress())

// 指定编码方式
app.use(compress({ encoding: 'gzip' }))

ETag — 条件请求缓存

import { etag } from 'hono/etag'

app.use(etag())
// 自动计算响应体的 ETag,支持 If-None-Match 条件请求
// 内容未变时返回 304 Not Modified,节省带宽

Basic Auth / Bearer Auth

import { basicAuth } from 'hono/basic-auth'
import { bearerAuth } from 'hono/bearer-auth'

// HTTP Basic 认证
app.use('/admin/*', basicAuth({
  username: 'admin',
  password: process.env.ADMIN_PASSWORD ?? 'secret',
}))

// Bearer Token 认证
app.use('/api/*', bearerAuth({
  token: process.env.API_TOKEN ?? 'my-secret-token',
}))
// 要求 Header: Authorization: Bearer <token>

Cache — 边缘缓存

import { cache } from 'hono/cache'

// 在 Cloudflare Workers 上使用 Cache API
app.use('/static/*', cache({
  cacheName: 'my-app-cache',
  cacheControl: 'max-age=3600',  // 缓存 1 小时
}))

全局 vs 路由级中间件

// 全局中间件:对所有路由生效
app.use(logger())
app.use(cors())

// 路由级中间件:仅对匹配路径生效
app.use('/api/*', bearerAuth({ token: 'secret' }))

// 单个路由的内联中间件
app.get('/sensitive', authMiddleware, rateLimitMiddleware, (c) => {
  return c.json({ data: 'sensitive data' })
})

自定义中间件

请求响应时间记录

// middleware/timing.ts
import { type MiddlewareHandler } from 'hono'

export const timingMiddleware: MiddlewareHandler = async (c, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  c.header('X-Response-Time', `${ms}ms`)
  console.log(`${c.req.method} ${c.req.path}${c.res.status} [${ms}ms]`)
}

app.use(timingMiddleware)

用户鉴权中间件

// middleware/auth.ts
import { type MiddlewareHandler } from 'hono'

// 定义类型扩展(让 c.get('user') 有正确类型)
type Variables = {
  user: { id: string; email: string }
}

export const authMiddleware: MiddlewareHandler<{ Variables: Variables }> = async (c, next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  try {
    // 验证 token(简化示例)
    const user = await verifyToken(token)
    c.set('user', user)  // 将用户信息传递给后续处理函数
    await next()
  } catch {
    return c.json({ error: 'Invalid token' }, 401)
  }
}

createMiddleware 工厂函数

当中间件需要接受配置参数时,使用 createMiddleware 创建带参数的中间件工厂:

import { createMiddleware } from 'hono/factory'

// 带参数的限流中间件
export const rateLimit = (options: {
  windowMs: number
  max: number
}) => createMiddleware(async (c, next) => {
  const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
  const key = `rate_limit:${ip}`

  // 这里简化了限流逻辑,实际应使用 KV/Redis
  const count = requestCounts.get(key) ?? 0

  if (count >= options.max) {
    return c.json(
      { error: 'Too many requests' },
      429,
      { 'Retry-After': String(options.windowMs / 1000) }
    )
  }

  requestCounts.set(key, count + 1)
  await next()
})

// 使用
app.use('/api/*', rateLimit({ windowMs: 60_000, max: 100 }))

中间件顺序很重要app.use() 的调用顺序决定了中间件的执行顺序。通常推荐:logger → cors → compress → 自定义安全中间件 → 路由处理。在注册路由之前调用 app.use(),否则中间件不会对已注册的路由生效。