# Interceptors

## Interceptors

인터셉터는 `@Injectable()`데코레이터로 주석이 달린 클래스입니다. 인터셉터는 `NestInterceptor` 인터페이스를 구현해야 합니다.

![](https://docs.nestjs.com/assets/Interceptors_1.png)

&#x20;인터셉터에는 [Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) (AOP) 기술에서 영감을 얻은 유용한 기능 세트가 있습니다. 그들은 다음을 가능하게 합니다.

* 메소드 실행 전/후에 **추가 로직** 바인딩
* 함수에서 반환 된 결과를 **변환**
* 함수에서 발생 된 예외를 **변환**&#x20;
* 기본 기능 **확장**
* 특정 조건에 따라 기능을 완전히 **재정의** (예 : 캐싱 목적)

## Basics

각 인터셉터는 두 개의 인수를받는 `intercept()`메소드를 구현합니다. 첫 번째는 `ExecutionContext`인스턴스입니다 ([guards](https://app.gitbook.com/guards)와 정확히 같은 객체). `ExecutionContext`는 `ArgumentsHost`에서 상속받습니다. 예외 필터 챕터에서 앞서 `ArgumentsHost`를 보았습니다. 여기서 우리는 원래 핸들러로 전달된 인수를 감싸는 래퍼이며 응용 프로그램의 유형에 따라 다른 인수 배열을 포함한다는 것을 알았습니다. 이 주제에 대한 자세한 내용은 [예외 필터](https://docs.nestjs.com/exception-filters#arguments-host)를 다시 참조하십시오.

## Execution context

`ExecutionContext`는 `ArgumentsHost`를 확장하여 현재 실행 프로세스에 대한 추가 정보를 제공합니다. 그 모습은 다음과 같습니다.

```typescript
export interface ExecutionContext extends ArgumentsHost {
  getClass<T = any>(): Type<T>;
  getHandler(): Function;
}
```

`getHandler()`메소드는 호출될 경로 핸들러에 대한 참조를 리턴합니다. `getClass()`메소드는 이 특정 핸들러가 속하는 `Controller` 클래스의 유형을 리턴합니다. 예를 들어, 현재 처리 된 요청이 `CatsController`의`create()`메소드로 예정된 `POST` 요청인 경우, `getHandler()`는 `create()` 메소드에 대한 참조를 리턴하고 `getClass()`는 `CatsController` **type** (인스턴스 아님)을 반환합니다.

## Call handler

두 번째 인수는 `CallHandler`입니다. `CallHandler` 인터페이스는 `handle()`메소드를 구현하는데, 인터셉터의 어느 시점에서 경로 핸들러 메소드를 호출하는 데 사용할 수 있습니다. `intercept()`메소드 구현에서 `handle()`메소드를 호출하지 않으면 라우트 핸들러 메소드가 전혀 실행되지 않습니다.

이 접근법은 `intercept()`메소드가 효과적으로 요청/응답 스트림을 **랩핑**한다는 것을 의미합니다. 결과적으로 최종 라우트 핸들러 실행 **전후**에 커스텀 로직을 구현할 수 있습니다. `handle()`을 호출하기 **전**에 실행되는 `intercept()` 메소드에 코드를 작성할 수 있다는 것이 분명하지만, 나중에 어떻게 되는지에 어떤 영향을 미칩니까? `handle()`메소드는 `Observable`을 리턴하므로, 강력한 [RxJS](https://github.com/ReactiveX/rxjs) 연산자를 사용하여 응답을 추가로 조작 할 수 있습니다. Aspect Oriented Programming 용어를 사용하여 라우트 핸들러의 호출 (즉, `handle()`호출)을 [Pointcut](https://en.wikipedia.org/wiki/Pointcut)이라고합니다. 추가 로직이 삽입됩니다.

예를 들어, 들어오는`POST/cats` 요청을 고려하십시오. 이 요청은 `CatsController` 안에 정의된 `create()`핸들러를 대상으로합니다. `handle()`메소드를 호출하지 않는 인터셉터가 도중에 호출되면 `create()`메소드가 실행되지 않습니다. `handle()`이 호출되고 (그리고 그것의 `Observable`이 리턴되면) `create()`핸들러가 트리거됩니다. 응답 스트림이 `Observable`을 통해 수신되면 추가 작업을 스트림에서 수행할 수 있으며 최종 결과는 호출자에게 반환됩니다.

## Aspect interception

우리가 살펴볼 첫번째 사용 사례는 인터셉터를 사용하여 사용자 상호 작용을 기록하는 것입니다 (예: 사용자 호출 저장, 비동기 적으로 이벤트 디스패치 또는 타임 스탬프 계산). 우리는 아래에 간단한 `LoggingInterceptor`를 보여줍니다:

```typescript
@@filename(logging.interceptor)
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`)),
      );
  }
}
@@switch
import { Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor {
  intercept(context, next) {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}
```

> info **힌트** `NestInterceptor<T,R>`는 `T`가 `Observable<T>`(응답 스트림을 지원)의 유형을 나타내고 `R`은`Observable<R>`에 의해 랩핑된 값의 유형인 일반 인터페이스입니다.
>
> warning **알림** 컨트롤러, 프로 바이더, 가드 등과 같은 인터셉터는`생성자`를 통해 **종속성을 주입 할 수 있습니다**.

`handle()`은 RxJS `Observable`을 반환하므로 스트림을 조작하는 데 사용할 수 있는 연산자를 다양하게 선택할 수 있습니다. 위의 예제에서 우리는 `tap()`연산자를 사용했는데, 이는 관찰 가능 스트림이 정상적으로 종료되거나 예외적으로 종료될 때 익명 로깅 기능을 호출하지만 응답주기를 방해하지는 않습니다.

## Binding interceptors

인터셉터를 설정하기 위해 `@nestjs/common` 패키지에서 가져온 `@UseInterceptors()`데코레이터를 사용합니다. [pipes](https://app.gitbook.com/%20pipes) 및 [guards](https://app.gitbook.com/%20guards)와 같이 인터셉터는 컨트롤러 범위, 방법 범위 또는 전역 범위 일 수 있습니다.

```typescript
@@filename(cats.controller)
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
```

> info **힌트** `@UseInterceptors()`데코레이터는 `@nestjs/common` 패키지에서 가져옵니다.

위의 구성을 사용하여 `CatsController`에 정의된 각 경로 핸들러는 `LoggingInterceptor`를 사용합니다. 누군가 `GET /cats` 엔드 포인트를 호출하면 표준 출력에 다음 출력이 표시됩니다.

```typescript
Before...
After... 1ms
```

인스턴스 대신 `LoggingInterceptor` 유형을 전달하여 프레임 워크의 인스턴스화와 종속성 주입을 가능하게 합니다. 파이프, 가드 및 예외 필터와 마찬가지로 내부 인스턴스도 전달할 수 있습니다.

```typescript
@@filename(cats.controller)
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}
```

언급한 바와 같이, 위의 구성은 인터셉터를이 컨트롤러가 선언한 모든 핸들러에 연결합니다. 인터셉터의 범위를 단일 방법으로 제한하려면 **메소드 수준**에서 데코레이터를 적용하면됩니다.

전역 인터셉터를 설정하기 위해 Nest 애플리케이션 인스턴스의 `useGlobalInterceptors()`메소드를 사용합니다.

```typescript
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
```

전역 인터셉터는 모든 컨트롤러와 모든 경로 핸들러에 대해 전체 애플리케이션에서 사용됩니다. 의존성 주입의 관점에서, 모듈 외부에서 등록된 전역 인터셉터 (위의 예에서와 같이 `useGlobalInterceptors()`로)는 의존성이 주입될 수 없습니다. 이는 모듈의 컨텍스트 외부에서 수행되기 때문입니다. 이 문제를 해결하기 위해 다음 구성을 사용하여 **모든 모듈에서 직접 인터셉터**를 설정할 수 있습니다.

```typescript
@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}
```

> info **힌트** 인터셉터에 대한 의존성 주입을 수행하기 위해이 접근법을 사용할 때, 이 구성이 사용되는 모듈에 관계없이, 인터셉터는 실제로는 전역적입니다. 어디에서 해야 합니까? 인터셉터 (위 예에서 `LoggingInterceptor`)가 정의된 모듈을 선택하십시오. 또한 커스텀 프로 바이더 등록을 다루는 유일한 방법은 `useClass`가 아닙니다. [여기](https://app.gitbook.com/fundamentals/custom-providers)에 대해 자세히 알아보십시오.

## Response mapping

우리는 `handle()`이 `Observable`을 반환한다는 것을 이미 알고 있습니다. 이 스트림에는 경로 처리기의 **반환** 값이 포함되어 있으므로 RxJS의 `map()`연산자를 사용하여 쉽게 변경할 수 있습니다.

> warning **경고** 응답 매핑 기능은 라이브러리 별 응답 전략에서 작동하지 않습니다 (`@Res()`객체를 직접 사용하는 것은 금지됨).

프로세스를 시연하기 위해 사소한 방식으로 각 응답을 수정하는 `TransformInterceptor`를 만들어 봅시다. RxJS의 `map()`연산자를 사용하여 응답 객체를 새로 생성된 객체의 `data` 속성에 할당하여 새 객체를 클라이언트에 반환합니다.

```typescript
@@filename(transform.interceptor)
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 })));
  }
}
@@switch
import { Injectable } from '@nestjs/common';
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor {
  intercept(context, next) {
    return next.handle().pipe(map(data => ({ data })));
  }
}
```

> info **힌트** Nest 인터셉터는 동기 및 비동기 `intercept()`메소드와 함께 작동합니다. 필요한 경우 단순히 메소드를 `비동기`로 전환 할 수 있습니다.

위의 구성에서 누군가가 `GET /cats` 엔드 포인트를 호출하면 응답은 다음과 같습니다 (라우트 핸들러가 빈 배열`[]`을 리턴한다고 가정).

```javascript
{
  "data": []
}
```

인터셉터는 전체 애플리케이션에서 발생하는 요구 사항에 대한 재사용 가능한 솔루션을 작성하는 데 큰 가치가 있습니다. 예를 들어, 각각의 `null` 값을 빈 문자열 `''`로 변환해야 한다고 상상해보십시오. 한 줄의 코드를 사용하여 인터셉터를 전역적으로 바인딩하여 등록된 각 핸들러에서 자동으로 사용할 수 있습니다.

```typescript
@@filename()
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 ));
  }
}
@@switch
import { Injectable } from '@nestjs/common';
import { map } from 'rxjs/operators';

@Injectable()
export class ExcludeNullInterceptor {
  intercept(context, next) {
    return next
      .handle()
      .pipe(map(value => value === null ? '' : value ));
  }
}
```

## Exception mapping

또 다른 흥미로운 사용 사례는 RxJS 의`catchError()`연산자를 사용하여 발생된 예외를 무시하는 것입니다.

```typescript
@@filename(errors.interceptor)
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())),
      );
  }
}
@@switch
import { Injectable, BadGatewayException } from '@nestjs/common';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor {
  intercept(context, next) {
    return next
      .handle()
      .pipe(
        catchError(err => throwError(new BadGatewayException())),
      );
  }
}
```

## Stream overriding

핸들러 호출을 완전히 막고 대신 다른 값을 반환하려는 몇가지 이유가 있습니다. 명백한 예는 응답 시간을 개선하기 위해 캐시를 구현하는 것입니다. 캐시에서 응답을 반환하는 간단한 **캐시 인터셉터**를 살펴 보겠습니다. 실제 예에서 우리는 TTL, 캐시 무효화, 캐시 크기 등과 같은 다른 요소를 고려하고 싶지만 이 논의의 범위를 벗어납니다. 여기에서는 기본 개념을 보여주는 기본 예제를 제공합니다.

```typescript
@@filename(cache.interceptor)
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();
  }
}
@@switch
import { Injectable } from '@nestjs/common';
import { of } from 'rxjs';

@Injectable()
export class CacheInterceptor {
  intercept(context, next) {
    const isCached = true;
    if (isCached) {
      return of([]);
    }
    return next.handle();
  }
}
```

우리의 `CacheInterceptor`는 하드 코딩된 `isCached` 변수와 하드 코딩된 응답 `[]`을 가지고 있습니다. 여기서 주목할 점은 RxJS `of()`연산자에 의해 생성된 새로운 스트림을 반환하므로 경로 핸들러는 **전혀 호출되지 않습니다**. 누군가 `CacheInterceptor`를 사용하는 엔드 포인트를 호출하면 응답 (하드 코딩된 빈 배열)이 즉시 리턴됩니다. 일반적인 솔루션을 만들기 위해 `Reflector`를 활용하고 사용자 정의 데코레이터를 만들 수 있습니다. `Reflector`는 [guards](https://app.gitbook.com/guards) 챕터에 잘 설명되어 있습니다.

## More operators

RxJS 연산자를 사용하여 스트림을 조작할 수 있으므로 많은 기능이 제공됩니다. 다른 일반적인 사용 사례를 고려해 봅시다. 경로 요청에서 **시간 초과**를 처리한다고 가정해 보십시오. 일정 시간 후에 엔드 포인트가 아무것도 리턴하지 않으면 오류 응답으로 종료하려고합니다. 다음과 같은 구성으로 가능합니다.

```typescript
@@filename(timeout.interceptor)
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(timeout(5000))
  }
}
@@switch
import { Injectable } from '@nestjs/common';
import { timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor {
  intercept(context, next) {
    return next.handle().pipe(timeout(5000))
  }
}
```

5 초 후에 요청 처리가 취소됩니다.
