中间件
中间件是在 HTTP 请求期间、请求到达路由处理程序之前执行的一系列函数。链中的每个函数都可以结束请求或将其转发给下一个中间件。
典型的 AdonisJS 应用程序使用中间件来解析请求体、管理用户会话、认证请求、服务静态资源等。
你也可以创建自定义中间件来在 HTTP 请求期间执行其他任务。
中间件栈
为了让你更好地控制中间件管道的执行,AdonisJS 将中间件栈分为以下三组。
服务器中间件栈
服务器中间件在每个 HTTP 请求上运行,即使你没有为当前请求的 URL 定义任何路由。
它们非常适合为不依赖框架路由系统的应用程序添加额外功能。例如,静态资源中间件被注册为服务器中间件。
你可以在 start/kernel.ts 文件中使用 server.use 方法注册服务器中间件。
import server from '@adonisjs/core/services/server'
server.use([
() => import('@adonisjs/static/static_middleware')
])路由器中间件栈
路由器中间件也称为全局中间件。它们在每个具有匹配路由的 HTTP 请求上执行。
Bodyparser、auth 和 session 中间件在路由器中间件栈下注册。
你可以在 start/kernel.ts 文件中使用 router.use 方法注册路由器中间件。
import router from '@adonisjs/core/services/router'
router.use([
() => import('@adonisjs/core/bodyparser_middleware')
])命名中间件集合
命名中间件是除非显式分配给路由或组,否则不会执行的中间件集合。
我们建议你创建专用的中间件类,将它们存储在命名中间件集合中,然后将它们分配给路由,而不是在路由文件中定义中间件作为内联回调。
你可以在 start/kernel.ts 文件中使用 router.named 方法定义命名中间件。确保导出命名集合以便能够在路由文件中使用它。
import router from '@adonisjs/core/services/router'
router.named({
auth: () => import('#middleware/auth_middleware')
})创建中间件
中间件存储在 ./app/middleware 目录中,你可以通过运行 make:middleware ace 命令创建新的中间件文件。
另请参阅:Make middleware 命令
node ace make:middleware user_location上述命令将在 middleware 目录下创建 user_location_middleware.ts 文件。
中间件表示为具有 handle 方法的类。在执行期间,AdonisJS 将自动调用此方法,并将 HttpContext 作为第一个参数传递给它。
// title: app/middleware/user_location_middleware.ts
import { HttpContext } from '@adonisjs/core/http'
import { NextFn } from '@adonisjs/core/types/http'
export default class UserLocationMiddleware {
async handle(ctx: HttpContext, next: NextFn) {
}
}在 handle 方法中,中间件必须决定是继续处理请求、通过发送响应来完成请求,还是引发异常来中止请求。
中止请求
如果中间件引发异常,所有即将到来的中间件和路由处理程序将不会执行,异常将交给全局异常处理程序。
import { Exception } from '@adonisjs/core/exceptions'
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class UserLocationMiddleware {
async handle(ctx: HttpContext, next: NextFn) {
const userLocation = ctx.request.header('user-location')
if (!userLocation) {
throw new Exception('无法检测用户位置', {
status: 400
})
}
await next()
}
}结束请求
中间件可以通过使用 response.send 方法发送响应来结束请求。此时,next 方法不应该被调用。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class MaintenanceModeMiddleware {
async handle({ response }: HttpContext, next: NextFn) {
if (isUnderMaintenance) {
return response.serviceUnavailable('稍后再试')
}
await next()
}
}调用下一个中间件
如果中间件决定继续处理请求,它必须调用 next 方法。
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class UserLocationMiddleware {
async handle({ request }: HttpContext, next: NextFn) {
const userLocation = request.header('user-location')
if (userLocation) {
// 将用户位置附加到请求
}
await next()
}
}将中间件分配给路由和路由组
命名集合中注册的中间件可以分配给路由或路由组。
// title: start/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
router
.get('dashboard', () => {})
.use(middleware.auth())你可以将多个中间件作为数组分配。
router
.get('dashboard', () => {})
.use([
middleware.auth(),
middleware.acl()
])分配给路由组
router
.group(() => {
router.get('dashboard', () => {})
router.get('settings', () => {})
})
.use(middleware.auth())中间件执行流程
AdonisJS 中间件管道使用责任链设计模式。通过此设计,中间件能够在请求(下游)和响应(上游)阶段执行操作。
以下是中间件管道如何处理请求的可视化表示:
┌─────────────────────────────────────────────────────────┐
│ 服务器中间件 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 路由器中间件 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 命名中间件 │ │ │
│ │ │ ┌─────────────────────────────────────────────┐ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ 路由处理程序 │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘依赖注入
中间件类使用 IoC 容器实例化;因此,你可以在中间件构造函数或 handle 方法中类型提示依赖项。
import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
import { NextFn } from '@adonisjs/core/types/http'
import { GeoIpService } from '#services/geo_ip_service'
@inject()
export default class UserLocationMiddleware {
constructor(protected geoIpService: GeoIpService) {
}
async handle(ctx: HttpContext, next: NextFn) {
const ip = ctx.request.ip()
ctx.location = await this.geoIpService.lookup(ip)
}
}向中间件传递数据
你可以在注册中间件时通过传递参数向中间件传递数据。例如:
// title: start/kernel.ts
export const middleware = router.named({
auth: () => import('#middleware/auth_middleware')
})// title: start/routes.ts
import { middleware } from '#start/kernel'
router
.get('dashboard', () => {})
.use(middleware.auth({
guards: ['web', 'api']
}))在上面的例子中,auth 中间件将在其 handle 方法中接收 options 对象作为第三个参数。
// title: app/middleware/auth_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
type AuthMiddlewareOptions = {
guards: string[]
}
export default class AuthMiddleware {
async handle(
ctx: HttpContext,
next: NextFn,
options: AuthMiddlewareOptions
) {
console.log(options)
await next()
}
}