NestJS Overview - Interceptors


Intercepterは、@Injectable()データレコーダに適用されるクラスである.インタフェースは、NestInterceptorインタフェースを実装する必要があります.

intercepterはAOP技術の中でいくつかの感心させる有用な能力を持っている.
  • メソッド実行前後付加論理バインド
  • 変換
  • 関数が返す結果
  • 変換
  • 関数放出の異常
  • 拡張基本関数挙動
  • 特定の条件に従って関数
  • を完全に上書きする.

    Basics


    各インタフェースは、intercept方法を実装しなければならない.この方法は2つの因子を受け入れる.第1因子はExecutionContext例であり、第2因子はCallHandler例である.

    Execution context

    ArgumentsHostExecutionContextを継承することによって、さまざまな現在の実行コンテキストに関する有用な情報を提供するために、いくつかの新しいヘルプメソッドも追加されます.詳細については、以下の文書で説明する.

    Call handler

    CallHandlerインタフェースは、インタフェース内部の必要に応じてルーティングハンドルメソッドを呼び出すためのhandle()メソッド実装を必要とする.intercept()メソッド内でhandle()メソッドを個別に呼び出さないと、ルーティングハンドルはまったく実行されません.
    この方法は、intercept()の方法により、要求/応答ストリームを効率的にパッケージする.その結果、カスタムロジックをルーティングハンドルの前後のすべての場所に追加できます.handle()メソッド呼び出しの前にロジックを追加することは明確ですが、呼び出し後のロジックはどのように処理すればいいのでしょうか.handle()メソッドの戻り値はObservableであるため、必要に応じてRxJS演算子を使用して応答を操作することができる.AOPの用語を用いる場合,ルーティングハンドラの呼び出しをPointcut(他の論理を挿入する場所を示す)と呼ぶ.
    例えば、POST /catsからの要求があると仮定する.要求は、CatsControllerの内部に定義されたcreate() Handlerにあるべきである.handle()メソッドを呼び出さないインタフェースが途中で呼び出されると、create()プロセッサがトリガーされます.その後、Observableを介して応答ストリームが受信されると、ストリーム上で追加の演算が実行された後、最後の結果は呼び出し元に返される.

    Aspect Interception


    最初の柚子ボックスを使用して表示されるインタフェースは、ユーザーのインタラクションを記録することです.次の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>はGenericインターフェースであり、TObservable<T>のタイプであり、RObservable<R>のタイプである.
    インタフェースは、他のコントローラ、プローブ、およびガードのように、作成者に依存性を注入することができる.handle()メソッドはRxJS Observableを返すので、複数の演算子を使用してストリームを操作することができる.上記の例では、tap()演算子を使用して、観測可能なストリームのエレガント/例外の終了時に匿名記録関数を呼び出すが、そうでなければ応答周期には関与しない.

    Binding Interceptors


    インタフェースを設定するには、@UseInterceptors()変換器を使用します.パイプやガードのように、intercepterはコントローラ/メソッド/グローバルスキャンで使用できます.
    @UseInterceptors(LoggingInterceptor)
    export class CatsController {}
    上記のコードにより、CatsControllerのすべてのルーティングハンドルがLoggingInterceptorを使用します.Get /cats個のエンドポイントのリクエストがある場合、以下の結果が出力されます.
    Before...
    After... 1ms
    上記のコードでは、LoggingInterceptorの例ではなく、ネストされた依存注入機能が使用される.もちろん、インスタンスをスキップすることもできます.
    @UseInterceptors(new LoggingInterceptor())
    export class CatsController {}
    メソッドスキャンにのみ適用したい場合は、メソッドレベルでアクセサリーを適用するだけです.
    グローバルインタフェースを設定するには、ネストされたアプリケーションインタフェースからuseGlobalInterceptors()メソッドを呼び出すことができます.
    const app = await NestFactory.create(AppModule);
    app.useGlobalInterceptors(new LoggingInterceptor());
    グローバルインタフェースは、アプリケーションのグローバルで使用され、すべてのコントローラのすべてのルーティングハンドルで実行されます.依存注入が必要な場合は、次の方法を任意のモジュールで使用できます.
    import { Module } from '@nestjs/common';
    import { APP_INTERCEPTOR } from '@nestjs/core';
    
    @Module({
      providers: [
        {
          provide: APP_INTERCEPTOR,
          useClass: LoggingInterceptor,
        },
      ],
    })
    export class AppModule {}

    Response mapping

    handle()Observableを返したのは私たちが知っている事実です.ストリームはルーティングハンドルから返される値を含むので、RxJSのmap()演算子で内容を変更できます.
    ライブラリ固有のレスポンスポリシーを使用すると、レスポンスマッピング機能が機能しない可能性があります.TransformInterceptorを作ってみます.この受信機は、各応答を明確な方法に変更します.RxJSのmap演算子を使用して、新しく作成されたオブジェクトのdataに応答オブジェクトを入れ、クライアントに返します.
    import { Injectable, NestInterceptor, ExecutionContext, CallHandler } 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, next: CallHandler): Observable<Response<T>> {
        return next.handle().pipe(map(data => ({ data })));
      }
    }
    Nest Intercepterはsync/async intercept()メソッドをサポートします.もしあなたが望むなら、交換すればいいです.
    上記のコードの作成後、要求がGET /catsエンドポイントに入ると、応答は以下のように表示される(ルーティングプロセッサが空の配列を返すと仮定する)
    {
      "data": []
    }
    Intercepterがアプリケーション全体で再利用可能なソリューションを作成することは、非常に価値があります.たとえば、nullのすべての値を空の文字列値''に変更する必要があるとします.1行のコードがインタフェースをグローバルにバインドする場合は、すべてのプロセッサに適用されます.
    import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    @Injectable()
    export class ExcludeNullInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next
          .handle()
          .pipe(map(value => value === null ? '' : value ));
      }
    }

    Exception mapping


    もう1つの興味深い例は、RxJScatchError演算子によって投げ出された例外である.
    import {
      Injectable,
      NestInterceptor,
      ExecutionContext,
      BadGatewayException,
      CallHandler,
    } from '@nestjs/common';
    import { Observable, throwError } from 'rxjs';
    import { catchError } from 'rxjs/operators';
    
    @Injectable()
    export class ErrorsInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next
          .handle()
          .pipe(
            catchError(err => throwError(new BadGatewayException())),
          );
      }
    }

    Stream overriding


    Handlerを呼び出すよりも、他の値を返す場合があります.たとえば、応答時間を短縮するためにキャッシュが実装される場合があります.次は簡単なキャッシュインタフェースです.このパラメータがキャッシュされている場合、キャッシュの値が呼び出されます.そうでない場合、Handlerが呼び出されます.
    import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
    import { Observable, of } from 'rxjs';
    
    @Injectable()
    export class CacheInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const isCached = true;
        if (isCached) {
          return of([]);
        }
        return next.handle();
      }
    }
    上記のCacheInterceptorは、ハードコーディングのisCached変数を有し、ハードコーディングの戻り値は[]である.ここで、RxJS of演算子によって新しいストリームが返されるため、ルーティングハンドルはまったく呼び出されません.アプリケーションのエンドポイントとしてキャッシュインタフェースを要求する人がいる場合、応答はすぐに返されます.一般的な解決策を策定するために、新しいベンチマークカウンタを作成し、内部でReflectorを利用することができる.

    More operators


    RxJS演算子を用いてストリームを操作する可能性は我々に多くの能力を与えた.他の普通の柚子箱を考えてみましょう.ルーティングリクエストのタイムアウトを処理したいと仮定します.エンドポイントが特定の時間内に何も返されない場合は、リクエストを終了し、エラー応答を返します.この機能は、次のコードで実現できます.
    import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
    import { Observable, throwError, TimeoutError } from 'rxjs';
    import { catchError, timeout } from 'rxjs/operators';
    
    @Injectable()
    export class TimeoutInterceptor implements NestInterceptor {
      intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next.handle().pipe(
          timeout(5000),
          catchError(err => {
            if (err instanceof TimeoutError) {
              return throwError(new RequestTimeoutException());
            }
            return throwError(err);
          }),
        );
      };
    };
    5秒後、応答処理は停止します.RequestTimeoutExceptionを投げる前に、より多くのカスタムロジックを入れることもできます.

    に感銘を与える


    Intercepterを利用して多くのコード重複を解消できると思います.実際のルーティングハンドルメソッドを呼び出す前と後に、追加のタイムアウトなどを設定することもでき、驚きましたが、まだ使ったことがないので、どれほど強力な機能なのか想像できません.
    最も重要なのは、RxJSのObservableと演算子についてよく知らないため、理解には一定の限界があるようだ.これらの友达は勉强してから见ると、もっとよく理解して、もっとよく使うことができます.