Exception Filter

이준엽·2024년 12월 14일
  1. exception layer

    • Nestjs는 기본적으로 unhandled exception을 처리하는 exception layer가 내장되어 있다.
    • 예외가 인식되지 않는 경우(HttpException이 아니고 상속도 하지 않는 경우) 기본 response를 보낸다.
      {
        "statusCode": 500,
        "message": "Internal server error"
      }
    • *미들웨어에서 발생한 에러는 글로벌 필터에서 밖에 처리할 수 없다.
  2. throwing standard exceptions

    • Nest는 기본적으로 HttpException 클래스를 제공한다.
    • 일반적인 HTTP REST/GraphQL API 기반 애플리케이션의 경우 특정 오류가 발생할 때 표준 HTTP응답을 보내는것을 권장.
    • HttpException 에는 응답을 결정하는 두 개의 필수와 한개의 선택적 파라미터를 사용
      • response - JSON response body를 정의한다. 에러의 원인을 정의하는 에러 객체로 사용될 수 있다. (message만 재정의 하려면 문자열을, JSON response body 전체를 재정의하려면 object를 제공하면된다.)
      • status - HTTP status code를 정의 (유효한 HTTP status code 여야 한다. @nsetjs/common의 HttpStatus enum을 import 해 사용하는 것을 추천)
      • options - cause속성을 지원하며 에러원인을 명시하는 대체적인 방법으로 사용됨(description)
    • JSON response body에는 두가지 properties가 존재
      • statusCode - default는 status에서 제공된 상태 코드
      • message - 상태에 따른 HTTP 오류에 관한 설명, response 파라미터에 문자열을 입력함으로써 재정의 할 수 있다.
    • 사용법 예시
    * @example
         * throw new HttpException()
         * throw new HttpException('message', HttpStatus.BAD_REQUEST)
         * throw new HttpException({ reason: 'this can be a human readable reason' }, HttpStatus.BAD_REQUEST)
         * throw new HttpException(new Error('Cause Error'), HttpStatus.BAD_REQUEST)
         * throw new HttpException('custom message', HttpStatus.BAD_REQUEST, {
         *  cause: new Error('Cause Error'),
         * })
  3. custom exceptions

    • built-in HTTP exceptions를 이용하면 돼서 필요성이 떨어진다.
    • 필요하다면 HttpException 클래스로부터 상속받아 만드는 것이 좋다. (Nestjs 예외를 인식하고 응답을 자동으로 처리 할 수 있다.)
    • monorepo에서 사용예시 찾아보기.
      export class AdditionalErrorsBadRequestException extends HttpException {
        constructor(objectOrError: AdditionalExceptionError) {
          super(HttpException.createBody(objectOrError), HttpStatus.BAD_REQUEST);
        }
      }
  4. built-in HTTP exceptions

    • BadRequestException
    • UnauthorizedException
    • NotFoundException
    • ForbiddenException
    • NotAcceptableException
    • RequestTimeoutException
    • ConflictException
    • GoneException
    • HttpVersionNotSupportedException
    • PayloadTooLargeException
    • UnsupportedMediaTypeException
    • UnprocessableEntityException
    • InternalServerErrorException
    • NotImplementedException
    • ImATeapotException
    • MethodNotAllowedException
    • BadGatewayException
    • ServiceUnavailableException
    • GatewayTimeoutException
    • PreconditionFailedException
  5. Exception filters

    • 기본으로 내장된 filter 말고도 exceptions layer에 대해 완전히 제어하고 싶은 경우 exceptionFilter를 implement받아 만들 수 있음
    • @Catch() 를 이용하여 필터가 특정 예외를 찾고있음을 알린다. 단일 유형 또는 쉼표로 구분하여 여러 유형에 대한 필터를 설정할 수 있다.
    • 그다음 filter의 catch(exception, host)메소드를 구현하고 원하는대로 로직을 만들 수 있다.
    • monorepo에서 찾아보기
      @Catch(AdditionalErrorsBadRequestException)
      export class AdditionalErrorsBadRequestExceptionFilter
        extends ExceptionProvider
        implements ExceptionFilter<BadRequestException>
      {
        constructor(
          protected readonly slackService: SlackService,
          httpResponseErrorMsg: HttpResponseErrorMsg,
        ) {
          super(slackService, httpResponseErrorMsg);
        }
      
        catch(exception: BadRequestException, host: ArgumentsHost): void {
          const ctx = host.switchToHttp();
          const response = ctx.getResponse<Response>();
          const status = exception.getStatus();
      
          const responseJson: ResponseJson = this.buildResponseJson(status);
      
          const err = exception.getResponse() as ExceptionError & {
            errorDetails: any;
          };
      
          responseJson.errors = [this.preProcessByClientError(err.message)];
          responseJson.errorDetails = err.errorDetails;
      
          response.status(status).json(responseJson);
        }
      }
  6. Arguments host

    • 예외가 발생한 환경(context)에 대한 정보를 제공한다.
      export interface ArgumentsHost {
          /**
           * Returns the array of arguments being passed to the handler.
           */
          getArgs<T extends Array<any> = any[]>(): T;
          /**
           * Returns a particular argument by index.
           * @param index index of argument to retrieve
           */
          getArgByIndex<T = any>(index: number): T;
          /**
           * Switch context to RPC.
           * @returns interface with methods to retrieve RPC arguments
           */
          switchToRpc(): RpcArgumentsHost;
          /**
           * Switch context to HTTP.
           * @returns interface with methods to retrieve HTTP arguments
           */
          switchToHttp(): HttpArgumentsHost;
          /**
           * Switch context to WebSockets.
           * @returns interface with methods to retrieve WebSockets arguments
           */
          switchToWs(): WsArgumentsHost;
          /**
           * Returns the current execution context type (string)
           */
          getType<TContext extends string = ContextType>(): TContext;
      }
    • 예를 들어, HTTP 애플리케이션 컨텍스트 내에서 필터를 사용하면 ArgumentsHost[request, response]배열이 포함된다. 그러나 현재 컨텍스트가 웹 소켓 애플리케이션인 경우 해당 컨텍스트에 적합한 [client, data]배열이 포함된다.
  7. Binding filters

    • 적용하고 싶은 컨트롤러나 엔드포인트(메소드)에 @UseFilters() 데코레이터를 사용하여 주입할 수 있다.(여러개 동시 적용 가능)
    • 전역 범위 필터 적용
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.useGlobalFilters(new HttpExceptionFilter());
        await app.listen(3000);
      }
      bootstrap();
    • 필터를 적용할때 인스턴스(new)보단 클래스를 사용하면 메모리 사용량에서 이점을 가져갈 수 있다. (useGlobalFilters에선 new를 사용해야 한다.)
  8. Catch everything

    • @Catch()만을 사용하면 모든 경우의 예외를 처리하게 된다.
    • 특정 유형의 예외를 처리하는 필터가 먼저 선언되고 그다음 모든 경우의 예외를 처리하는 필터가 선언되어야 할 것 같지만 필터 체이닝 과정은 반대로 가장 일반적인 경우를 먼저 선언해야 제대로 작동할 수 있다.
    • Inheritance
      • 완전히 새로운 커스텀 필터가 아닌 기본으로 제공되는 전역 예외 필터를 확장하여 원하는대로 동작을 재정의 할 수도 있다.
      • BaseExceptionFilter를 상속받아서 확장.
        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);
          }
        }

참고: https://dkrnfls.tistory.com/83, https://velog.io/@haron/NestJS-Lifecycle-Events

profile
하루하루 차근차근

0개의 댓글