中间件 Middleware

导读

中间件是一个在路由处理程序之前调用的函数。中间件函数可以访问 requestresponse 对象,以及应用程序请求-响应周期中的 next() 中间件函数。next 中间件函数通常由名为 next 的变量表示。

img

默认情况下,嵌套中间件相当于 express 中间件。以下来自官方 express 文档的描述描述了中间件的功能:

中间件函数可以执行以下任务:
  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件函数。
  • 如果当前中间件函数未结束请求-响应周期,则必须调用 next() 以 将控制权传递给下一个中间件函数。否则,请求将悬而未决。

您可以在函数中或使用 @Injectable() 装饰器的类中实现自定义 Nest 中间件。该类应实现 NestMiddleware 接口,而该函数没有任何特殊要求。让我们从使用类方法实现一个简单的中间件功能开始。

Expressfastify 处理中间件的方式不同,并提供不同的方法签名,更多信息请阅读这里

logger.middleware
ts
import { Injectable, NestMiddleware } from '@nestjs/common'
import { NextFunction, Request, Response } from 'express'

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...')
    next()
  }
}
js
import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware {
  use(req, res, next) {
    console.log('Request...');
    next();
  }
}

依赖注入

Nest 中间件完全支持依赖注入。与提供程序和控制器一样,它们能够注入同一模块中可用的依赖项。与往常一样,这是通过构造函数完成的。

应用中间件

@Module()装饰器中没有中间件的位置。相反,我们使用模块类的configure()方法来设置它们。包含中间件的模块必须实现NestModule接口。让我们在AppModule级别设置LoggerMiddleware

ts
TS
ts
// app.module
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { LoggerMiddleware } from './common/middleware/logger.middleware'
import { CatsModule } from './cats/cats.module'

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats')
  }
}

在上面的例子中,我们为之前在CatsController中定义的/cats路由处理程序设置了LoggerMiddleware。我们还可以在配置中间件时将包含路由path和请求method的对象传递给forRoutes()方法,从而进一步将中间件限制为特定的请求方法。在下面的例子中,请注意我们导入了RequestMethod枚举来引用所需的请求方法类型。

ts
TS
ts
// app.module
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'
import { LoggerMiddleware } from './common/middleware/logger.middleware'
import { CatsModule } from './cats/cats.module'

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET })
  }
}
提示

可以使用 async/await 使 configure() 方法异步(例如,您可以在 configure() 方法主体内 await 异步操作的完成)。

警告

使用 express 适配器时,NestJS 应用程序将默认从包 body-parser 注册 jsonurlencoded。这意味着如果您想通过 MiddlewareConsumer 自定义该中间件,则需要在使用 NestFactory.create() 创建应用程序时通过将 bodyParser 标志设置为 false 来关闭全局中间件。

路由通配符

还支持基于模式的路由。例如,星号用作通配符,可匹配任意字符组合:

ts
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

'ab*cd' 路由路径将匹配 abcdab_cdabecd 等。字符 ?+*() 可在路由路径中使用,是其正则表达式对应项的子集。连字符 (-) 和点 (.) 由基于字符串的路径按字面解释。

警告

fastify 包使用最新版本的 path-to-regexp 包,该包不再支持通配符星号 *。相反,您必须使用参数(例如 (.*):splat*)。

中间件消费者

MiddlewareConsumer 是一个辅助类。它提供了几种内置方法来管理中间件。所有这些方法都可以以 流畅风格 简单地 链接forRoutes() 方法可以采用单个字符串、多个字符串、RouteInfo 对象、控制器类甚至多个控制器类。在大多数情况下,您可能只会传递一个用逗号分隔的 控制器 列表。下面是一个带有单个控制器的示例:

ts
TS
ts
// app.module
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { LoggerMiddleware } from './common/middleware/logger.middleware'
import { CatsModule } from './cats/cats.module'
import { CatsController } from './cats/cats.controller'

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController)
  }
}
提示

apply() 方法可以采用单个中间件,也可以采用多个参数来指定多个中间件

排除路由

有时我们想排除某些路由而不应用中间件。我们可以使用 exclude() 方法轻松排除某些路由。此方法可以采用单个字符串、多个字符串或 RouteInfo 对象来标识要排除的路由,如下所示:

ts
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController)
提示

exclude() 方法使用 path-to-regexp 包支持通配符参数。

通过上面的例子,LoggerMiddleware 将绑定到 CatsController 中定义的所有路由,除了传递给 exclude() 方法的三个路由。

函数式中间件

我们一直使用的 LoggerMiddleware 类非常简单。它没有成员,没有附加方法,也没有依赖项。为什么我们不能只在一个简单的函数中定义它而不是类呢?事实上,我们可以。这种类型的中间件称为函数式中间件。让我们将记录器中间件从基于类的中间件转变为函数式中间件来说明区别:

ts
TS
ts
// logger.middleware
import { NextFunction, Request, Response } from 'express'

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`)
  next()
};

And use it within the AppModule:

app.module
ts
consumer
  .apply(logger)
  .forRoutes(CatsController)
提示

当您的中间件不需要任何依赖项时,请考虑使用更简单的功能中间件替代方案。

多个中间件

如上所述,为了绑定按顺序执行的多个中间件,只需在 apply() 方法内提供一个逗号分隔的列表:

ts
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController)

全局中间件

如果我们想一次将中间件绑定到每个已注册的路由,我们可以使用 INestApplication 实例提供的 use() 方法:

main
ts
const app = await NestFactory.create(AppModule)
app.use(logger)
await app.listen(3000)
提示

无法在全局中间件中访问 DI 容器。您可以在使用 app.use() 时改用 [功能性中间件](中间件# functional-middleware)。或者,您可以使用类中间件并在 AppModule(或任何其他模块)中使用 .forRoutes('*') 来使用它。