中间件
中间件在端点的 Handler 之前或之后运行。我们可以在请求分发前获取 Request,或在分发后操作 Response。
中间件的定义
- 处理程序:应返回
Response对象。只会调用一个处理程序。 - 中间件:应执行
await next()且不返回任何值,以调用下一个中间件;或者返回Response提前结束处理。
与处理程序一样,可以使用 app.use 或 app.HTTP_METHOD 注册中间件。这使得指定路径和方法十分简单。
// match any method, all routes
app.use(logger())
// specify path
app.use('/posts/*', cors())
// specify method and path
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',
})
)注意
在 Deno 中,可以使用与 Hono 版本不同的中间件,但这可能导致错误。 例如,以下代码因版本不同而无法工作。
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() 中编写自己的中间件:
// Custom logger
app.use(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
// Add a custom header
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() 会限制其复用性。因此,可以将中间件拆分到独立文件。
拆分中间件时,可以使用 Hono 工厂中的 createMiddleware(),确保保留 context 和 next 的类型定义。它还允许后续处理程序以类型安全的方式访问通过 Context 的 set 方法存储的数据。
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})说明
可以为 createMiddleware 使用泛型:
createMiddleware<{Bindings: Bindings}>(async (c, next) =>在 Next 之后修改响应
此外,也可以让中间件在需要时修改响应:
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})在中间件参数中访问上下文
要在中间件参数中访问上下文,直接使用 app.use 提供的上下文参数即可。以下示例说明具体做法。
import { cors } from 'hono/cors'
app.use('*', async (c, next) => {
const middleware = cors({
origin: c.env.CORS_ORIGIN,
})
return middleware(c, next)
})在中间件中扩展上下文
在中间件中使用 c.set 扩展上下文。将 { Variables: { yourVariable: YourVariableType } } 作为泛型参数传给 createMiddleware 函数,即可保证类型安全。
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!'))
})链式中间件之间的类型推断
通过 .use() 链式调用多个中间件时,Hono 会自动累积 Variables 类型。位于中间件链之后的路由处理程序,可以类型安全地访问所有前置中间件提供的变量:
import { createMiddleware } from 'hono/factory'
const authMiddleware = createMiddleware<{
Variables: { user: { id: string; name: string } }
}>(async (c, next) => {
c.set('user', { id: '123', name: 'Alice' })
await next()
})
const dbMiddleware = createMiddleware<{
Variables: { db: { query: (sql: string) => Promise<unknown> } }
}>(async (c, next) => {
c.set('db', {
query: async (sql) => {
/* ... */
},
})
await next()
})
const app = new Hono()
.use(authMiddleware)
.use(dbMiddleware)
.get('/', (c) => {
// Both `user` and `db` are available and type-safe
const user = c.var.user // { id: string; name: string }
const db = c.var.db // { query: (sql: string) => Promise<unknown> }
return c.json({ user })
})这是因为每次 .use() 调用都会返回包含合并后类型的新 Hono 实例,因此随着中间件链增长,类型也不断累积。在大多数场景下,这省去了预先手动声明合并 Env 类型的需要。
第三方中间件
内置中间件不依赖外部模块,而第三方中间件可以依赖第三方库,因此使用它们可以构建更复杂的应用。
我们可以选用各种第三方中间件。 例如 GraphQL 服务端中间件、Sentry 中间件、Firebase 身份验证中间件等。