Menu
CoddyTech

Interceptor

CoddyのNode.jsジャーニー「TypeScriptとNestJS」セクションの一部。レッスン 23/24。

各ハンドラーに手を加えず、すべての応答に処理時間を含めたいとします。インターセプターはハンドラーを包み込みます。next.handle()の前にあるコードはハンドラーの実行前に動き、next.handle()が返す値に対するコードは実行後に動きます。

intercept(context: ExecutionContext, next: CallHandler) {
    console.log('before');
    return next.handle().pipe(
        tap(() => console.log('after')),
    );
}

next.handle()は、RxJSのObservableとしてハンドラーの結果を返します。これは後から届く値です。.pipe(tap(...))は、その値が届いたときに関数を実行し、Nestが値を送信する前に処理します。このpipeは、Nestのバリデーションパイプとは関係ありません。ガードはすべてのインターセプターより前に実行されるため、ガードが拒否したリクエストはインターセプターに到達しません。パイプを適用したObservableを返します。Nestはそれが保持する値を送信します。context.switchToHttp().getResponse()はresを返し、そのsetHeaderでヘッダーを追加できます。

app.useGlobalInterceptors(...)はすべてのルートにインターセプターを適用し、@UseInterceptors(...)は1つのコントローラーに適用します。

challenge icon

チャレンジ

簡単

「Run」を実行すると、src/main.tsを使ってトラッカーが再び起動します。個別の練習用ファイルはツリー内に残しておいてかまいません。このサーバーはそれらを起動しません。

クライアントや独自の監視機能は、各リクエストにかかった時間を知りたがっています。どのルートにも手を加えず、成功したすべてのレスポンスにレスポンスヘッダーX-Response-Timeを追加しましょう。

  1. src/common/response-time.interceptor.tsを開きます。ResponseTimeInterceptorのinterceptメソッドで、まずDate.now()を使って現在時刻を記録します。interceptの先頭にあるコードは、ルートのメソッドより前に実行されます。
  2. context.switchToHttp().getResponse()を使って、Expressのresを取得します。
  3. next.handle()はルートのメソッドを実行し、その結果をRxJSのObservable(後から値が届く値)として返します。引き続きそれを返しますが、Observableの.pipe()メソッドを使い、すでにrxjsからインポートされているtapに渡します。tapに渡す関数は、結果が届いたとき、Nestがそれを送信する前に実行されます。その関数内でres.setHeaderを呼び出し、X-Response-Timeに、記録した時刻からの経過時間をミリ秒単位で設定し、その後ろにmsを付けます。たとえば3msのようになります。(RxJSの.pipe()は、第3章のバリデーションパイプとは関係ありません。Observableに処理をつなげるものです。)
  4. src/main.tsで、パイプとフィルターの隣にapp.useGlobalInterceptors(...)を使ってアプリ全体にインターセプターを登録し、新しいResponseTimeInterceptorを渡します。
  5. レスポンスボディは変更してはいけません。GET /tasksは引き続き{ "items": [...], "page": 1, "total": 3, "pages": 2 }を返します。

試してみましょう:GET /tasksを送信して、レスポンスのHeadersタブを開きます。0msや2msのような値を持つX-Response-Timeが表示されます。GET /statsにもこのヘッダーが付き、x-api-key: tasks-key-123ヘッダーを付けて送信した{ "title": "Buy milk" }のPOSTにも付きます。次に、そのPOSTをキーなしで送信します。403となり、X-Response-Timeは付きません。Nestはインターセプターの前にガードを実行するため、インターセプターが開始する前にガードがリクエストを拒否しました。ルート内でエラーがスローされた場合もヘッダーは付きません。tapは結果が届いたときにのみ実行されるためです。

自分で試してみよう

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class ResponseTimeInterceptor implements NestInterceptor {
    intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
        // 時刻を記録し、ハンドラーを実行してから、X-Response-Time を設定します。
        return next.handle();
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

TypeScriptとNestJSのすべてのレッスン

自分で練習してみよう: Expressプレイグラウンド