Exception filters

Exception filters

Nest에는 애플리케이션에서 처리되지 않은 모든 예외를 처리하는 내장 예외 레이어가 제공됩니다. 응용 프로그램 코드에서 예외를 처리하지 않으면이 계층에서 예외를 포착하여 적절한 사용자 친화적인 응답을 자동으로 보냅니다.

기본적으로 이 동작은 내장 된 전역 예외 필터에 의해 수행되는데, 이 예외는 HttpException 유형 (및 그 하위 클래스)의 예외를 처리합니다. 예외가 unrecognized 인 경우 ( HttpException 또는 HttpException에서 상속되는 클래스가 아님) 클라이언트는 다음과 같은 기본 JSON 응답을받습니다.

{
  "statusCode": 500,
  "message": "Internal server error"
}

Base exceptions

내장 된 HttpException클래스는 @nestjs/common 패키지에서 공개됩니다.

CatsController에는 findAll() 메소드 (GET 경로 핸들러)가 있습니다. 어떤 이유로 이 라우트 핸들러에서 예외가 발생한다고 가정해 봅시다. 이를 설명하기 위해 다음과 같이 하드 코딩합니다.

@@filename(cats.controller)
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

info 힌트 여기서는 HttpStatus를 사용했습니다. 이것은 @nestjs/common 패키지에서 가져온 헬퍼 열거형입니다.

클라이언트가 이 엔드 포인트를 호출하면 응답은 다음과 같습니다.

{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpException 생성자는 응답을 결정하는 두 가지 필수 인수를 사용합니다.

  • response 인수는 JSON 응답 본문을 정의합니다. 아래에 설명 된대로 문자열 또는 객체일 수 있습니다.

  • status 인수는 HTTP 상태 코드를 정의합니다.

기본적으로 JSON 응답 본문에는 두 가지 속성이 있습니다.

  • statusCode: status 인수에 제공된 HTTP 상태 코드로 기본 설정

  • message: 상태에 기반한 HTTP 에러에 대한 간단한 설명

JSON 응답 본문의 메시지 부분 만 재정의하려면 response 인수에 문자열을 제공하십시오.

전체 JSON 응답 본문을 재정의하려면 response 인수에 객체를 전달하십시오. Nest는 객체를 직렬화하여 JSON 응답 본문으로 반환합니다.

두 번째 생성자 인자 status는 유효한 HTTP 상태 코드 여야합니다. 모범 사례는@nestjs/common에서 가져온 HttpStatus 열거 형을 사용하는 것입니다.

다음은 전체 응답 본문을 재정의 하는 예입니다.

@@filename(cats.controller)
@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}

위의 방법을 사용하면 다음과 같이 응답이 나타납니다.

{
  "status": 403,
  "error": "This is a custom message"
}

Exceptions hierarchy

예외 계층 구조를 직접 만드는 것이 좋습니다. 이것은 사용자 정의 HTTP 예외가 기본 HttpException 클래스에서 상속되어야 함을 의미합니다. 결과적으로 Nest는 예외를 인식하고 자동으로 오류 응답을 처리합니다. 그러한 커스텀 예외를 구현해 봅시다 :

@@filename(forbidden.exception)
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

ForbiddenException은 기본 HttpException을 확장하므로 내장 예외 핸들러와 완벽하게 작동하므로 findAll() 메소드 내에서 사용할 수 있습니다.

@@filename(cats.controller)
@Get()
async findAll() {
  throw new ForbiddenException();
}

HTTP exceptions

상용구 코드를 작성할 필요성을 줄이기 위해 Nest는 코어 HttpException에서 상속되는 사용 가능한 예외 세트를 제공합니다. 이들 모두는 @nestjs/common 패키지에서 공개됩니다:

  • BadRequestException

  • UnauthorizedException

  • NotFoundException

  • ForbiddenException

  • NotAcceptableException

  • RequestTimeoutException

  • ConflictException

  • GoneException

  • PayloadTooLargeException

  • UnsupportedMediaTypeException

  • UnprocessableEntityException

  • InternalServerErrorException

  • NotImplementedException

  • BadGatewayException

  • ServiceUnavailableException

  • GatewayTimeoutException

Exception filters

기본 (내장) 예외 필터가 많은 경우를 자동으로 처리 할 수 있지만 예외 레이어를 완전히 제어 할 수 있습니다. 예를 들어, 일부 동적 요인에 따라 로깅을 추가하거나 다른 JSON 스키마를 사용할 수 있습니다. 예외 필터는 이 목적을 위해 설계되었습니다.

HttpException클래스의 인스턴스인 예외를 포착하고 이에 대한 사용자 정의 응답 로직을 구현하는 예외 필터를 작성해 봅시다.

@@filename(http-exception.filter)
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
@@switch
import { Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

info 힌트 모든 예외 필터는 일반 ExceptionFilter <T> 인터페이스를 구현해야 합니다. 이를 위해서는catch (exception: T, host: ArgumentsHost)메소드에 표시된 서명을 제공해야 합니다. T는 예외 유형을 나타냅니다.

@Catch (HttpException) 데코레이터는 필요한 메타 데이터를 예외 필터에 바인딩하여 이 특정 필터가 HttpException유형의 예외를 찾고 있음을 Nest에게 알려줍니다. @Catch() 데코레이터는 단일 매개 변수 또는 쉼표로 구분된 목록을 사용할 수 있습니다. 이를 통해 여러 유형의 예외에 대한 필터를 한 번에 설정할 수 있습니다.

Arguments host

catch() 메소드의 매개 변수를 보자. exception 매개 변수는 현재 처리중인 예외 객체입니다. host 매개 변수는 ArgumentsHost 객체입니다. ArgumentsHostoriginal 요청 핸들러 (예외가 발생한 곳)에 전달된 인수를 감싸는 래퍼입니다. 여기에는 응용 프로그램 및 사용중인 플랫폼의 유형에 따라 특정 인수 배열이 포함됩니다. 다음은 ArgumentsHost의 모습입니다 :

export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}

ArgumentsHost는 다양한 애플리케이션 컨텍스트에서 기본 배열에서 올바른 인수를 선택하는 데 도움이 되는 편리한 메소드 세트를 제공합니다. 다시 말해, ArgumentsHost인자 배열에 지나지 않습니다. 예를 들어, HTTP 애플리케이션 컨텍스트 내에서 필터를 사용하면 ArgumentsHost[request, response]배열이 포함됩니다. 그러나 현재 컨텍스트가 웹 소켓 애플리케이션인 경우 해당 컨텍스트에 적합한 [client, data]배열이 포함됩니다. 이 방법을 사용하면 사용자 정의 catch()메소드에서 원래 핸들러로 전달되는 인수에 액세스 할 수 있습니다.

Binding filters

새로운 HttpExceptionFilterCatsControllercreate()메소드에 묶어 봅시다.

@@filename(cats.controller)
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@@switch
@Post()
@UseFilters(new HttpExceptionFilter())
@Bind(Body())
async create(createCatDto) {
  throw new ForbiddenException();
}

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

우리는@UseFilters()데코레이터를 사용했습니다. @Catch()데코레이터와 유사하게 단일 필터 인스턴스 또는 쉼표로 구분된 필터 인스턴스 목록을 사용할 수 있습니다. 여기서는 HttpExceptionFilter인스턴스를 작성했습니다. 또는 인스턴스 대신 클래스를 전달하여 프레임 워크에 인스턴스화에 대한 책임을 남기고 종속 주입을 활성화 할 수 있습니다.

@@filename(cats.controller)
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@@switch
@Post()
@UseFilters(HttpExceptionFilter)
@Bind(Body())
async create(createCatDto) {
  throw new ForbiddenException();
}

info 힌트 가능한 경우 인스턴스 대신 클래스를 사용하여 필터를 적용하는 것이 좋습니다. Nest가 전체 모듈에서 동일한 클래스의 인스턴스를 쉽게 재사용할 수 있으므로 메모리 사용량이 줄어 듭니다.

위의 예에서 HttpExceptionFilter는 단일 create()라우트 핸들러에만 적용되어 메소드 범위가 됩니다. 예외 필터는 메소드 범위, 컨트롤러 범위 또는 전역 범위의 다른 수준으로 범위를 지정할 수 있습니다. 예를 들어, 필터를 컨트롤러 범위로 설정하려면 다음을 수행하십시오.

@@filename(cats.controller)
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

이 구조는 CatsController 안에 정의된 모든 경로 핸들러에 대해 HttpExceptionFilter를 설정합니다.

전역 범위 필터를 만들려면 다음을 수행하십시오.

@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

warning 경고 useGlobalFilters () 메소드는 게이트웨이 또는 하이브리드 애플리케이션에 대한 필터를 설정하지 않습니다.

전역 범위 필터는 모든 컨트롤러와 모든 경로 처리기에 대해 전체 응용 프로그램에서 사용됩니다. 의존성 주입의 관점에서, 모듈 외부에서 등록 된 전역 필터 (위의 예에서와 같이useGlobalFilters()로)는 모듈의 컨텍스트 외부에서 수행되기 때문에 의존성을 주입할 수 없습니다. 이 문제를 해결하기 위해 다음 구성을 사용하여 모든 모듈에서 직접 전역 필터를 등록 할 수 있습니다.

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

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

info 힌트 이 접근 방식을 사용하여 필터에 대한 의존성 주입을 수행할 때 이 구성이 사용되는 모듈에 관계없이 필터는 실제로 전역적입니다. 어디에서 해야 합니까? 필터 (위 예에서 HttpExceptionFilter)가 정의 된 모듈을 선택하십시오. 또한 커스텀 프로 바이더 등록을 다루는 유일한 방법은 useClass가 아닙니다. 여기에 대해 자세히 알아보십시오.

이 기술을 사용하여 필요한만큼 필터를 추가 할 수 있습니다. 간단히 공급자 배열에 각각을 추가하십시오.

Catch everything

처리되지 않은 예외 (예외 유형에 관계없이)를 모두 잡으려면 @Catch()데코레이터의 매개 변수 목록을 비워 두십시오 (예: @Catch()).

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

위의 예에서 필터는 유형 (클래스)에 관계없이 발생 된 각 예외를 포착합니다.

Inheritance

일반적으로 응용 프로그램 요구 사항을 충족하도록 제작된 완전히 사용자 지정된 예외 필터를 만듭니다. 그러나 내장된 기본 전역 예외 필터를 단순히 확장하고 특정 요인에 따라 동작을 재 정의하려는 경우 사용 사례가 있을 수 있습니다.

예외 처리를 기본 필터에 위임하려면 BaseExceptionFilter를 확장하고 상속 된 catch()메소드를 호출해야합니다.

@@filename(all-exceptions.filter)
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
@@switch
import { Catch } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception, host) {
    super.catch(exception, host);
  }
}

warning 경고 BaseExceptionFilter를 확장하는 메서드 범위 및 컨트롤러 범위 필터는 new로 인스턴스화하면 안됩니다. 대신 프레임 워크에서 자동으로 인스턴스화하십시오.

위의 구현은 접근 방식을 보여주는 쉘일뿐입니다. 확장 예외 필터 구현에는 맞춤형 비즈니스 논리 (예: 다양한 조건 처리)가 포함됩니다.

전역 필터 는 기본 필터를 확장 할 수 있습니다. 이것은 두 가지 방법 중 하나로 수행 할 수 있습니다.

첫 번째 방법은 커스텀 글로벌 필터를 인스턴스화 할 때 HttpServer 참조를 삽입하는 것입니다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();

두 번째 방법은 여기에 표시된 APP_FILTER토큰을 사용하는 것입니다.

Last updated