Nest Interceptor

概述

拦截器是具有注解为 @Injectable() 的类, 应该实现了 NestInterceptor 接口。拦截器可以拦截在请求之后路由接收之前,或是路由响应之后。

拦截器受 AOP (切面编程,Aspect Oriented Programming) 影响,支持如下功能:

基础

每个拦截器实现 intercept() 方法,它接收两个参数,第一个是 ExecutionContext 实例,第二个参数为 CallHandler

执行上下文

继承了 ArgumentHost, ExecutionContext 添加若干新的 helper 方法提供关于当前执行上下文进程的更多的信息。

Call handler

CallHandler 接口实现了 handle() 方法,以便在拦截器中调用路由处理器方法。如果不在 intercept() 中调用 handle(),路由处理器方法将不会执行。

这种方式意味着 interceptor() 高效地包装了请求和响应流。因此在最终的路由处理器执行前或后可以实现自定义逻辑。我们非常清楚在 intercept() 方法的 handle() 之前执行一些自定义的代码,但如何影响后续?handle() 返回一个 Observable 对象,我们可以使用强大的 RxJS 去操作响应。使用面向切面编程,路由处理器的调用(如 handle())被称之为切入点(PointCut),表示在某个时间点上插入额外的逻辑。

截取切面 (Aspect interception)

import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  interceptor(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...')
    const now = Date.now();
    return next.handle().pipe( // handle() 返回 RxJS Observable
      tap(() => console.log(`After... ${Date.now() - now}ms`)),
    )
  }
}

// 绑定拦截器
@UseInterceptor(LogginInterceptor) // 类
// @UseInterceptors(new LoggingInterceptor()) 实例
export class CatController {}

app.useGlobalInterceptors(new LoggingInterceptor()) // 全局过滤器

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    }
  ]
})

响应映射

handle() 返回一个 Observable 对象。这个流包含了从路由处理器返回的值,可通过 RxJS 中的 map() 修改值。

// transform.interceptor.ts
import { Obserable } 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: ExecutioinContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(map(data => ({ data })));
  }
}

异常映射

RxJs 的 catchError() 覆写抛出的异常

import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  interceptor(context: ExceptionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(catchError(err => throwError(new BadGatewayException())))
  }
}

流覆写

import { Observable, of } from 'rxjs';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  interceptor(context: ExecutionContext, next: CallHandler): Observable<any> {
    const isCached = true;
    if (isCached) {
      return of ([]);
    }
    return next.handle();
  }
}

更多操作

// 处理超时的请求,若一段时间不响应,直接返回一个错误的响应
@Injectable()
export class TimeoutInterceptor implemens NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        catchError(err => {
          if (err instanceof TimeoutError) {
            return throwError(new RequestTimeoutException());
          }
          return throwError(err);
        })
      })
    )
  }
}