在线文档教程

拦截器

拦截器

拦截器是一个用@Injectable()装饰器注释的类。拦截器应该实现NestInterceptor接口。

拦截器具有一系列有用的功能,这些功能受到面向切面编程(AOP)技术的启发。他们可以:

  • 在方法执行之前/之后绑定额外的逻辑

基本

每个拦截器都有intercept()方法,它带有2个参数。第一个是ExecutionContext实例(与看守器完全相同的对象)。ExecutionContext继承自ArgumentsHost(之前在“异常过滤器”一章中看到过)。ArgumentsHost是传递给原始处理程序的参数的包装器,它根据应用程序的类型在引擎下包含不同的参数数组。

export interface ArgumentsHost { getArgs<T extends Array<any> = any[]>(): T; getArgByIndex<T = any>(index: number): T; switchToRpc(): RpcArgumentsHost; switchToHttp(): HttpArgumentsHost; switchToWs(): WsArgumentsHost; }

ArgumentsHost提供了一组有用的方法,帮助我们从底层数组中选择正确的参数。换句话说,ArgumentsHost只不过是一个参数数组。例如,当在HTTP应用程序上下文中使用guard时,ArgumentsHost将包含[request, response]数组。但是,当当前上下文是Web套接字应用程序时,此数组将等于[client, data]。此设计决策使您可以访问最终将传递给相应处理程序的任何参数。

ExecutionContext提供的多一点点。它扩展了ArgumentsHost,而且提供了当前执行过程的更多详细信息。

export interface ExecutionContext extends ArgumentsHost { getClass<T = any>(): Type<T>; getHandler(): Function; }

所述getHandler()返回一个参考当前处理的处理程序,而getClass()返回的类型Controller此特定处理程序属于类别。使用换句话说,如果用户指向create()方法被定义和内注册CatsController时,getHandler()将返回一个参考create()方法和getClass()在这种情况下,将简单地返回一个CatsController类型(未实例)。

第二个参数是a call$,一个Observable流。如果不返回此流,则根本不会评估主处理程序。这是什么意思?基本上,这call$是一个推迟最终处理程序执行的流。比方说,有人提出了POST /cats请求。此请求指向在其中create()定义的处理程序CatsController。如果call$沿途调用未返回流的拦截器,则不会create()评估该方法。仅当call$返回流时,才会触发最终方法。为什么?因为Nest 订阅了返回的流,并使用此流生成的值来为最终用户创建单个响应或多个响应。而且,正如刚才所说,call$是一个Observable意思,它为我们提供了一组非常强大的运算符,可以帮助我们进行响应操作。

截取方面

第一个用例是使用拦截器在函数执行之前或之后添加额外的逻辑。当我们要记录与应用程序的交互时,例如存储用户调用,异步调度事件或计算时间戳,这很有用。举个例子,让我们创建一个简单的LoggingInterceptor

logging.interceptor.ts

JS

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept( context: ExecutionContext, call$: Observable<any>, ): Observable<any> { console.log('Before...' const now = Date.now( return call$.pipe( tap(() => console.log(`After... ${Date.now() - now}ms`)), } }

提示这NestInterceptor<T, R>是一个通用接口,其中T指示处理的类型Observable<T>(在流后面),而R返回的值的返回类型返回Observable<R>。

注意拦截器的作用与控制器,提供者,警卫等相同,这意味着它们可以通过注入注入依赖关系constructor

由于call$是RxJSObservable,我们可以使用很多各种操作符来操作流。在上面的例子中,我们使用了tap()在可观察序列的正常或异常终止时调用函数的运算符。

为了设置拦截器,我们使用@UseInterceptors()@nestjs/common包中导入的装饰器。与警卫一样,拦截器也可以是控制器作用域,方法作用域和全局作用域。

cats.controller.ts

JS

@UseInterceptors(LoggingInterceptor) export class CatsController {}

提示@UseInterceptors()装饰器从导入的@nestjs/common包。

多亏了这个,定义的每个路由处理程序都CatsController将使用LoggingInterceptor。当有人调用GET /cats端点时,您将在控制台窗口中看到以下输出:

Before... After... 1ms

请注意,我们传递的是LoggingInterceptor类型而不是实例,使框架具有实例化责任并启用依赖注入。另一种可用的方法是传递立即创建的实例:

cats.controller.ts

JS

@UseInterceptors(new LoggingInterceptor()) export class CatsController {}

如前所述,上面的构造将拦截器附加到此控制器声明的每个处理程序。如果我们决定只限制其中一个,我们只需要在方法级别设置拦截器。为了绑定全局拦截器,我们使用useGlobalInterceptors()Nest应用程序实例的方法:

const app = await NestFactory.create(ApplicationModule app.useGlobalInterceptors(new LoggingInterceptor()

全局拦截器用于整个应用程序,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局拦截器(如上例中所示)不能注入依赖关系,因为它们不属于任何模块。为了解决这个问题,您可以使用以下构造直接从任何模块设置防护:

app.module.ts

JS

import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; @Module{ providers: [ { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }, ], }) export class ApplicationModule {}

  • 提示替代选项是使用执行上下文功能。此外,useClass这不是处理自定义提供程序注册的唯一方法。在这里了解更多。

响应映射

我们已经知道这call$是一个Observable。该对象包含从路由处理程序返回的值,因此我们可以使用map()运算符轻松改变它。

警告响应映射功能不适用于特定于库的响应策略(@Res()禁止直接使用该对象)。

让我们创建TransformInterceptor将通过分配data新创建的对象的属性来获取每个响应并对其进行修改。

transform.interceptor.ts

JS

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response<T> { data: T; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept( context: ExecutionContext, call$: Observable<T>, ): Observable<Response<T>> { return call$.pipe(map(data => { data })) } }

HINTNest拦截器就像使用异步intercept()方法的魅力一样,这意味着,async如果需要,您可以毫不费力地切换方法。

之后,当有人调用GET /cats端点时,请求将如下所示(我们假设路由处理程序返回一个空数组[]):

{ "data": [] }

在创建整个应用程序中使用的可重用解决方案时,拦截器具有巨大的潜力。例如,让我们假设我们需要将每个发生的null值转换为空字符串''。我们可以使用一行代码并将拦截器绑定为全局代码。多亏了这一点,每个注册的处理程序都会自动重用它。

JS

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class ExcludeNullInterceptor implements NestInterceptor { intercept( context: ExecutionContext, call$: Observable<any>, ): Observable<any> { return call$.pipe(map(value => value === null ? '' : value ) } }

异常映射

另一个有趣的用例是利用catchError()运算符来覆盖抛出的异常:

errors.interceptor.ts

JS

import { Injectable, NestInterceptor, ExecutionContext, HttpStatus, } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable() export class ErrorsInterceptor implements NestInterceptor { intercept( context: ExecutionContext, call$: Observable<any>, ): Observable<any> { return call$.pipe( catchError(err => throwError(new HttpException('Message', HttpStatus.BAD_GATEWAY)), ), } }

流重写

有时我们可能希望完全阻止调用处理程序并返回不同的值(例如,由于性能问题而从缓存中),有几个原因。一个很好的例子是缓存拦截器,它将使用一些TTL存储缓存的响应。不幸的是,这个功能需要更多的代码,并且由于简化,我们将仅提供一个应该简要解释主要概念的基本示例。

cache.interceptor.ts

JS

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { of } from 'rxjs/observable/of'; @Injectable() export class CacheInterceptor implements NestInterceptor { intercept( context: ExecutionContext, call$: Observable<any>, ): Observable<any> { const isCached = true; if (isCached) { return of([] } return call$; } }

这里CacheInterceptor有一个硬编码isCached变量和硬编码响应[]。我们在这里返回了一个通过of运算符创建的新流,因此根本不会调用路由处理程序。当有人调用使用的端点时CacheInterceptor,响应(硬编码的空数组)将立即返回。为了创建通用解决方案,您可以利用Reflector并创建自定义装饰器。在Reflector很好的描述警卫章。

返回流的可能性为我们提供了许多可能性。让我们考虑另一个常见的用例。想象一下,你想要处理超时。当您的端点在一段时间后没有返回任何内容时,我们希望以错误响应进行响应。

timeout.interceptor.ts

JS

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { timeout } from 'rxjs/operators'; @Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept( context: ExecutionContext, call$: Observable<any>, ): Observable<any> { return call$.pipe(timeout(5000)) } }

5秒后,请求处理将被取消。