拦截器
拦截器
拦截器是一个用@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 }))
}
}
HINT
Nest拦截器就像使用异步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秒后,请求处理将被取消。