中间件
中间件在处理函数之前或之后运行。它既可以在分发请求前获取 Request,也可以在分发后处理 Response。
中间件的定义
- 处理函数(Handler):必须返回
Response对象,仅会调用一个处理函数。 - 中间件(Middleware):无需返回值,通过
await next()将控制权交给下一个中间件。
用户可以像注册处理函数一样,使用 app.use 或 app.HTTP_METHOD 注册中间件,这样就能方便地指定路径和方法。
// 匹配任意方法、所有路由
app.use(logger())
// 指定路径
app.use('/posts/*', cors())
// 指定方法与路径
app.post('/posts/*', basicAuth())如果处理函数返回了 Response,该响应会直接返回给终端用户,同时停止后续处理。
app.post('/posts', (c) => c.text('Created!', 201))在上述示例中,在最终处理函数执行前会按照如下顺序处理四个中间件:
logger() -> cors() -> basicAuth() -> *handler*执行顺序
中间件的执行顺序由注册顺序决定。 第一个中间件在 next 之前的逻辑最先执行,next 之后的逻辑则最后执行。 示例如下:
app.use(async (_, next) => {
console.log('middleware 1 start')
await next()
console.log('middleware 1 end')
})
app.use(async (_, next) => {
console.log('middleware 2 start')
await next()
console.log('middleware 2 end')
})
app.use(async (_, next) => {
console.log('middleware 3 start')
await next()
console.log('middleware 3 end')
})
app.get('/', (c) => {
console.log('handler')
return c.text('Hello!')
})运行结果如下:
middleware 1 start
middleware 2 start
middleware 3 start
handler
middleware 3 end
middleware 2 end
middleware 1 end请注意:如果处理函数或任意中间件抛出异常,Hono 会捕获它,并将错误传给 app.onError() 回调,或自动转换成 500 响应再沿着中间件链返回。因此 next() 本身不会抛错,无需额外编写 try/catch/finally。
内置中间件
Hono 自带了一些中间件。
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(poweredBy())
app.use(logger())
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)WARNING
在 Deno 中,你可以单独指定中间件的版本,但这可能导致 Bug。 例如以下代码因为版本不一致而无法工作:
import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'
const app = new Hono()
app.get(
'/ws',
upgradeWebSocket(() => ({
// ...
}))
)自定义中间件
可以直接在 app.use() 内编写自定义中间件:
// 自定义日志
app.use(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
// 添加自定义响应头
app.use('/message/*', async (c, next) => {
await next()
c.header('x-message', 'This is middleware!')
})
app.get('/message/hello', (c) => c.text('Hello Middleware!'))不过将中间件直接写在 app.use() 中会限制其复用性。更好的做法是把中间件拆分到单独文件中。
为了在拆分时不丢失 context 与 next 的类型定义,可以使用 Hono 工厂提供的 createMiddleware()。这样还能在后续处理函数中安全访问通过 Context.set 写入的数据。
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})INFO
可以在 createMiddleware 中使用泛型:
createMiddleware<{Bindings: Bindings}>(async (c, next) =>在 next 之后修改响应
如有需要,中间件也可以在 next() 之后修改响应:
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})在中间件参数中访问 Context
如果想在中间件参数中访问上下文,可直接使用 app.use 提供的 context 参数,示例如下:
import { cors } from 'hono/cors'
app.use('*', async (c, next) => {
const middleware = cors({
origin: c.env.CORS_ORIGIN,
})
return middleware(c, next)
})在中间件中扩展 Context
若要在中间件中扩展上下文,请使用 c.set。可以通过给 createMiddleware 传入 { Variables: { yourVariable: YourVariableType } } 泛型参数来保证类型安全。
import { createMiddleware } from 'hono/factory'
const echoMiddleware = createMiddleware<{
Variables: {
echo: (str: string) => string
}
}>(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})第三方中间件
内置中间件没有外部依赖,而第三方中间件可以依赖更多第三方库,帮助我们构建更复杂的应用。
你可以在第三方中间件列表中探索各种方案,例如 GraphQL 服务中间件、Sentry 中间件、Firebase Auth 中间件等。