拦截器
拦截器是一个使用 @Injectable()
装饰器注释的类,并实现 NestInterceptor
接口。
拦截器具有一组有用的功能,受到面向切面编程(AOP)技术的启发。 它们使以下操作成为可能:
- 在方法执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据特定条件(例如,用于缓存目的)完全覆盖函数
基础知识
每个拦截器都实现了 intercept()
方法,
该方法接受两个参数。
第一个是 ExecutionContext
实例(与守卫的对象完全相同)。
ExecutionContext
继承自 ArgumentsHost
。
我们在异常过滤器章节中已经看到过 ArgumentsHost
。
在那里,我们看到它是对传递给原始处理程序的参数的包装,根据应用程序类型包含不同的参数数组。
您可以回到异常过滤器章节了解更多关于这个主题的信息。
执行上下文
通过扩展 ArgumentsHost
,ExecutionContext
还添加了几个新的辅助方法,提供有关当前执行过程的额外详细信息。
这些详细信息对于构建更通用的拦截器,在广泛的控制器、方法和执行上下文中工作,是有帮助的。
在这里了解有关ExecutionContext
的更多信息。
调用处理程序
第二个参数是 CallHandler
。CallHandler
接口实现了 handle()
方法,
您可以在拦截器的某个点使用它来调用路由处理程序方法。
如果在 intercept()
方法的实现中没有调用 handle()
方法,路由处理程序方法将根本不会被执行。
这种方法意味着 intercept()
方法有效地包装了请求/响应流。
因此,您可以在执行最终路由处理程序之前和之后实现自定义逻辑。
很明显,您可以在 intercept()
方法中编写在调用 handle()
之前执行的代码,
但是如何影响之后发生的事情呢?因为 handle()
方法返回一个 Observable
,
我们可以使用强大的 RxJS 操作符来进一步操作响应。使用面向切面编程的术语,
路由处理程序的调用(即调用 handle()
)被称为切点(Pointcut),
表明这是我们插入附加逻辑的点。
例如,考虑一下传入的 POST /cats
请求。
此请求将发送到 CatsController
内定义的 create()
处理程序。
如果在任何地方调用了不调用 handle()
方法的拦截器,create()
方法将不会被执行。
一旦调用了 handle()
(并且其 Observable
已经返回),将触发 create()
处理程序。
并且一旦通过 Observable
收到响应流,就可以在流上执行其他操作,并将最终结果返回给调用方。
切面拦截
我们首先将看一下使用拦截器记录用户交互的用例(例如,存储用户调用、异步调度事件或计算时间戳)。
下面展示了一个简单的 LoggingInterceptor
:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
NestInterceptor<T, R>
是一个泛型接口,其中T
表示Observable<T>
(支持响应流)的类型,
而R
是由Observable<R>
包装的值的类型。
拦截器,就像控制器、提供者、守卫等一样,可以通过其构造函数注入依赖项。
由于handle()
返回一个 RxJS Observable
,我们可以选择广泛使用操作符来操作流。
在上面的示例中,我们使用了tap()
操作符,它在可观察流的正常或异常终止时调用我们的匿名日志函数,
但不干扰响应周期以外的任何操作。