Nest 공식문서 Exception Filter

송은우·2022년 3월 25일
0

Exception Filters

Exception filter는 단순하게 exception 을 nest 전체에서 handling 해주는 된다. exception이 코드내에서 따로 처리되지 않는다면 exception filter로 이동해 응답을 전송한다.
Http exception그 하위클래스를 핸들링 하는 global exception filter가 내장되어 있는데,

{
"statusCode": 500,
"message": "Internal server error"
}
이 형태를 반환하는 filter이다.
global Exception filter는 http-errors library를 부분적으로 지원한다. status code가 있고, message가 있다면 그것을 반환해 준다.

Throw Standard Exception

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

같은 방식으로 exception을 만들어 낸다. HttpStatus는 @nestjs/common 패키지에 있다.

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

같은 방식으로 객체를 넘겨줄 수 있는데, 객체를 넘겨주면 완전히 응답이 바뀌고, 그냥 문자열만 입력하면 message 부분만 바뀐다

Custom Exceptions

Exception을 직접 만들 수 있는데, HttpException을 상속받는 방식으로 제작한다.

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}
@Get()
async findAll() {
  throw new ForbiddenException();
}

같은 방식으로 사용된다.

Built-in HTTP Exception

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

Exception Filters

built-in exception filter는 진짜 많은 것들을 자동으로 처리해 주지만, 상황에따라 다른 처리를 하는 것은 할 수 없다. 예를 들면 특정 exception 발생시 log를 남기는 것 같은 것들이 있다. 이때 exception filter를 사용하면 된다

커스텀 logic 을 이용하기에 ArgumentsHost 를 받고, 거기서 추출하는 req,res를 추출하는 방식으로 적용시킬 수 있다.

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,
      });
  }
}

모든 HttpException이 발생했을 때 이 이런 방식으로 처리하는 것이 가능하다.
모든 exception filter는 ExceptionFilter를 implements해야 한다.
@Catch 데코레이터는 특정 Exception만 잡을 수 있도록 하고, ,를 이용한 구분으로 여러 Exception을 잡을 수 있다.

Arguments Host

catch() 메서드를 보면exception 과 host 를 parameter에서 받을 수 있는데, exception은 지금 현재 처리되고 있는 Exception 객체를 의미하고, host Parameter는 ArgumentHost를 가져오는데, ArgumentHost는 utility 객체이다.
Req,Res같은 것을 얻을 수 있다. ArgumentHost
Argument Host는 되게 추상화가 많이 되어 있는데, 그 이유는 websocket, microservice 모두에서 사용할 수 있기 때문이다.

Binding Filters

@Post()
@UseFilters(new HttpExceptionFilter())
//@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

UseFilters를 이용해서 filter를 적용할 수 있다. 당연히 , 로 구분하면 여러개 동시 적용이 가능하다. 객체를 넣을 수도 있지만, class를 넣어서 DI와 책임을 framework로 조금 더 옮기는 방식도 가능하다.
가능하다면 class로 넣는 것이 instance보다 좋은데, memory 사용량에서 차이가 생긴다. instance는 모든 모듈에서 쉽게 공유될 수 있다.
UseFilters는 method scope, controller scope, global scope에서 적용할 수 있다.

//클래스
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

//전역
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

단 useGlobalFilters 메서드는 gateway(websocket)나 hybrid applications(react native)에서 제대로 역할을 못 한다.

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

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

저렇게 안에서 생성하면, useGlobalFilters라고 하는 어떤 모듈에도 포함되지 않는 곳에서 생성되는데, DI관점에서 좋지 않은 코드가 되기에, 위의 코드처럼 직접 넣을 수 있다.
단 이때는 어떤 모듈에서 등록하든 전부 global filter로 작동한다. provider에 배열로 넣어서 많은 것들을 등록할 수 있다.

Catch Everything

@Catch()만 사용했을 경우 모든 경우에서 사용할 수 있다.
이때 아래 코드처럼 작성한다면 platform 에 관계 없이 어떤 상황에서도 잘 작동한다.

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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

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

    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

일단 이런식으로 플랫폼 독립적인 것도 가능은 하다...

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);
  }
}

이런 방식으로 기본 global filter를 사용하고, 추가로 조금 수정하는 방식이 가능함.
method-scope, csontroller-scope에 해당하는 BaseExceptionFilter를 extend하는 것은 new로 인스턴스화 하지 말아야 한다.

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

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

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

첫 방법으로 만든 플랫폼 독립적인거는 이런 방식으로 처리할 수 있다.

profile
학생의 마음가짐으로 최선을 다하자

0개의 댓글