iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 9
0
Modern Web

Nest.js framework 30天初探系列 第 9

Nestjs framework 30天初探:Day09 Interceptors

Interceptors

https://ithelp.ithome.com.tw/upload/images/20171212/20107195WYk8kH7T3C.png

Interceptor是一個帶有@Interceptor()裝飾器的class,Interceptor需要實作NestInterceptor介面。

  1. Interceptor有以下四個主功能,作者在設計這個class時,是啟發於AOP(Aspect-Oriented Programming)
  • bind extra logic before / after method execution。
  • transform the result returned from the function。
  • transform the exception thrown from the function。
  • completely override the function depending on the chosen conditions (e.g. caching purposes)。
  1. AOP連結所舉的log機制,用AOP模式去撰寫會更顯低耦合好維護,所以我們透過實作log機制來一窺Interceptor樣貌,請先建立一個Interceptors資料夾,並在底下新增logging.interceptor.ts。
    cmd 指令:
cd src/modules/Shared & mkdir Interceptors

src/modules/Shared/Interceptors/logging.interceptor.ts

import { Interceptor, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';

@Interceptor()
export class LoggingInterceptor implements NestInterceptor {
    /*
   1.dataOrRequest參數代表你可以傳入expressjs中的request object、或經由microservice、websocket傳遞的data。
   2.ExecutionContext帶有兩個成員,parent和handler,其中,parent代表哪個Controller,handler是route handler的參考。
   3.stream$ 是Observable,可以使用各種Observable的方法。
   */
    intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
        console.log('在執行方法之前...');
        const now = Date.now();
        /*Observable do 解釋請看http://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-do
         */
        return stream$.do(() => { console.log(`在執行方法之後...${Date.now() - now}ms`) })
    }
}

說明:請看註解,基本上很像Guards的說明XDDD。
Interceptor跟Component、Controller、Guard和Middleware一樣,都可以透過constructor依賴注入。

  1. 完成了LoggingInterceptor,我們將其使用在UsersController,新增testInterceptor()。
    src/modules/Users/users.controller.ts
    部分程式碼
import { UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from '../Shared/Interceptors/logging.interceptor';

@Controller()
//@UseGuards(RolesGuard)記得註解此行
export class UsersController {
...省略...
@Get('testInterceptor')
    @UseInterceptors(LoggingInterceptor)
    async testInterceptor( @Request() req, @Response() res, @Next() next) {
        console.log('執行testInterceptor()');
        res.status(HttpStatus.OK).json();
    }
}

Interceptor可以作用在Method、Controller和全域,跟Guard一樣,nestjs很多地方的設計都有點類似。

  1. 接著打開Postman,對http://localhost:3000/testInterceptor 發出HTTP GET請求,console結果如下。
    https://ithelp.ithome.com.tw/upload/images/20171212/20107195be1F7QEkyS.png

如此,log機制建立就更為簡單,而且Interceptor加上Rxjs真的很方便,對於事件流可以有更多處理。
Response mapping

  1. 新增TransformInterceptor。
    程式碼如下:
    src/modules/Shared/Interceptors/transform.interceptor.ts
import { Interceptor, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Interceptor()
export class TransformInterceptor implements NestInterceptor {
    intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
        return stream$.map((data) => ({ "data": "be transformed" }));
    }
}

預期會得到{ "data": "be transformed" }。

5.1 運用在UsersController,新增testTransformInterceptor()。
src/modules/Users/users.controller.ts

@Get('testTransformInterceptor')
@UseInterceptors(TransformInterceptor)
async testTransformInterceptor( @Request() req, @Response() res, @Next() next) {
        res.status(HttpStatus.OK).json();
}

5.2 打開Postman,對http://localhost:3000/testTransformInterceptor 進行HTTP GET請求,結果如下。
https://ithelp.ithome.com.tw/upload/images/20171212/20107195FmemXKDdYy.png

說明:不符合預期,nestjs作者提醒,使用@Res() object,response mapping無法作用。

5.3 修改testTransformInterceptor()。
src/modules/Users/users.controller.ts

@Get('testTransformInterceptor')
    @UseInterceptors(TransformInterceptor)
    async testTransformInterceptor( ) {
        return "test response";
    }

5.4 打開Postman,對http://localhost:3000/testTransformInterceptor 進行HTTP GET請求,結果如下。
https://ithelp.ithome.com.tw/upload/images/20171212/20107195D7ZAznOyqA.png

說明:符合預期。

Exception mapping

  1. 因為stream$ 是Observable,所以我們可以使用catch()。
    src/modules/Shared/Interceptors/exception.interceptor.ts
import { Interceptor, NestInterceptor, ExecutionContext, HttpStatus } from '@nestjs/common';
import { HttpException } from '@nestjs/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Interceptor()
export class ExceptionInterceptor implements NestInterceptor {
intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
    return stream$.catch((err) =>
        Observable.throw(
            new HttpException('Exception interceptor 發威,exception被catch到', HttpStatus.BAD_GATEWAY)
            ))
    }
}

6.1 將ExceptionInterceptor運用在UsersController,新增testExceptionInterceptor()。
src/modules/Users/users.controller.ts

@Get('testExceptionInterceptor')
@UseInterceptors(ExceptionInterceptor)
async testExceptionInterceptor(@Request() req, @Response() res, @Next() next ) {
        throw new Error('test ExceptionInterceptor');
}

6.2 打開Postman,對http://localhost:3000/testExceptionInterceptor 進行HTTP GET請求,結果如下。
https://ithelp.ithome.com.tw/upload/images/20171212/20107195BPhod3kpH3.png

說明:ExceptionInterceptor()有攔截到並做出回應,這個其實也可以被Exception Filters捕捉到。

6.3 修改一下UsersController中的testExceptionInterceptor(),加上HttpExceptionFilter()。
src/modules/Users/users.controller.ts

@Get('testExceptionInterceptor')
@UseInterceptors(ExceptionInterceptor)
@UseFilters(new HttpExceptionFilter())
async testExceptionInterceptor(@Request() req, @Response() res, @Next() next ) {
    throw new Error('test ExceptionInterceptor');
}

6.4 打開Postman,對http://localhost:3000/testExceptionInterceptor 進行HTTP GET請求,結果如下。
https://ithelp.ithome.com.tw/upload/images/20171212/20107195rr88xI87xM.png

說明:有HttpExceptionFilter被捕捉到。

到這邊我們已經知道Interceptor有何功用了,對請求和回應可以做更細緻的處理。

程式碼在github


上一篇
Nestjs framework 30天初探:Day08 Guards
下一篇
Nestjs framework 30天初探:Day10 Unit Test & E2E Test
系列文
Nest.js framework 30天初探30

尚未有邦友留言

立即登入留言