跳至内容

中间件

中间件在处理程序之前/之后执行。我们可以获取Request在调度之前,或在调度之后操作Response

中间件定义

  • 处理程序 - 应该返回Response对象。只调用一个处理程序。
  • 中间件 - 应该不返回值,将使用await next()继续执行下一个中间件。

用户可以使用app.use或使用app.HTTP_METHOD以及处理程序来注册中间件。此功能使指定路径和方法变得容易。

ts
// match any method, all routes
app.use(logger())

// specify path
app.use('/posts/*', cors())

// specify method and path
app.post('/posts/*', basicAuth())

如果处理程序返回Response,它将用于最终用户,并停止处理。

ts
app.post('/posts', (c) => c.text('Created!', 201))

在这种情况下,四个中间件将在调度之前按如下方式进行处理。

ts
logger() -> cors() -> basicAuth() -> *handler*

执行顺序

中间件的执行顺序由其注册顺序决定。第一个注册的中间件的next之前的过程先执行,next之后的过程最后执行。见下文。

ts
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 有内置中间件。

ts
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 版本不同的中间件版本,但这会导致错误。例如,这段代码无法工作,因为版本不同。

ts
import { Hono } from 'jsr:@hono/[email protected]'
import { upgradeWebSocket } from 'jsr:@hono/[email protected]/deno'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket(() => ({
    // ...
  }))
)

自定义中间件

您可以在app.use()中直接编写自己的中间件。

ts
// 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()中会限制其可重用性。因此,我们可以将中间件分离到不同的文件中。

为了确保在分离中间件时不会丢失contextnext的类型定义,可以使用 Hono 工厂中的createMiddleware()

ts
import { createMiddleware } from 'hono/factory'

const logger = createMiddleware(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

信息

类型泛型可以与createMiddleware一起使用。

ts
createMiddleware<{Bindings: Bindings}>(async (c, next) =>

在 Next 之后修改响应

此外,可以设计中间件以在必要时修改响应。

ts
const stripRes = createMiddleware(async (c, next) => {
  await next()
  c.res = undefined
  c.res = new Response('New Response')
})

在中间件参数中访问上下文

要在中间件参数中访问上下文,请直接使用app.use提供的上下文参数。请参阅以下示例以了解说明。

ts
import { cors } from 'hono/cors'

app.use('*', async (c, next) => {
  const middleware = cors({
    origin: c.env.CORS_ORIGIN,
  })
  return middleware(c, next)
})

在中间件中扩展上下文

要在中间件中扩展上下文,请使用c.set。您可以通过向createMiddleware函数传递{ Variables: { yourVariable: YourVariableType } }泛型参数来使它类型安全。

ts
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 身份验证中间件等。

在 MIT 许可下发布。