首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

异常过滤器

内置的异常层负责处理整个应用程序中的所有抛出异常。捕获未处理的异常时,最终用户将收到适当的用户友好响应。

开箱即用,此操作由内置的全局异常过滤器执行,每个异常的异常都由全局异常过滤器处理,当它无法识别时(既不是HttpException也不是继承的类HttpException),则内置异常过滤器会生成以下默认JSON响应:

代码语言:javascript
复制
{
  "statusCode": 500,
  "message": "Internal server error"
}

抛出标准异常

Nest提供了一个内置HttpException类,从@nestjs/common包中公开。对于典型的基于HTTP REST / GraphQL API的应用程序,最佳实践是在发生某些错误情况时发送标准HTTP响应对象。

CatsController,我们有一个create()方法(POST路由处理程序)。让我们假设这个路由处理程序由于某种原因会抛出异常。我们要硬编码:

cats.controller.ts

JS

代码语言:javascript
复制
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

提示我们在HttpStatus这里使用了。它是从@nestjs/common包导入的辅助枚举器。

当客户端调用此端点时,响应将如下所示:

代码语言:javascript
复制
{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpException构造函数有两个参数,其确定响应:

  • response参数定义JSON响应体。可以是astring 或a object,如下所述。
  • status参数定义HTTP状态代码

默认情况下,JSON响应主体包含两个属性:

  • statusCode:默认为status参数中提供的HTTP状态代码
  • message:基于的HTTP错误的简短描述 status

要仅覆盖JSON响应主体的消息部分,请在response参数中提供一个字符串。要覆盖整个JSON响应主体,请在response参数中传递一个对象。Nest将序列化对象,并将其作为JSON响应正文返回。

第二个构造函数参数--status应该是有效的HTTP状态代码。最佳做法是使用HttpStatus从中导入的枚举@nestjs/common

cats.controller.ts

JS

代码语言:javascript
复制
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}

使用上面的代码,这就是响应的样子:

代码语言:javascript
复制
{
  "status": 403,
  "error": "This is a custom message"
}

自定义异常

在许多情况下,您将不需要编写自定义异常,而可以使用内置的Nest HTTP异常,如下一节所述。如果确实需要创建自定义的异常,则最好创建自己的异常层次结构,在该层次结构中,自定义异常将从基HttpException类继承。使用这种方法,Nest可以识别您的异常,并自动处理错误响应。让我们实现这样一个自定义异常:

forbidden.exception.ts

JS

代码语言:javascript
复制
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

由于ForbiddenException扩展了基础HttpException,它可以很好地与核心异常处理程序一起使用,因此我们可以在create()方法中使用它。

cats.controller.ts

JS

代码语言:javascript
复制
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

HTTP异常

为了减少样板代码,Nest提供了一组从核心继承的可用异常HttpException。所有这些都从@nestjs/common包装中暴露出来:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

异常过滤器

虽然基本(内置)异常过滤器可以为您自动处理许多情况,但您可能希望完全控制异常层。例如,您可能要添加日志记录或基于一些动态因素使用其他JSON模式。异常过滤器正是为此目的而设计的。它们使您可以控制精确的控制流程以及发送回客户端的响应内容。

让我们创建一个异常过滤器,该过滤器负责捕获作为HttpException类实例的异常,并为其实现自定义响应逻辑。为此,我们需要访问基础平台RequestResponse对象。我们将访问该Request对象,以便我们可以取出原始对象url并将其包含在日志记录信息中。我们将使用方法使用Response对象来直接控制所发送的响应response.json()

HTTP-exception.filter.ts

JS

代码语言:javascript
复制
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    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,
      });
  }
}

提示所有异常过滤器都应实现通用ExceptionFilter<T>接口。它强制您为catch(exception: T, host: ArgumentsHost)方法提供有效签名。T表示异常的类型。

@Catch(HttpException)装饰结合所需的元数据的异常过滤器,告诉Nest,这个特殊的过滤器正在寻找HttpException,没有别的。实际上,@Catch()装饰器可能会采用无数个参数,因此您可以为几种类型的异常设置过滤器,只需用逗号分隔即可。

exception属性是当前处理的异常,host而是一个ArgumentsHost对象。它ArgumentsHost是已传递给原始处理程序的参数的包装器,它根据应用程序的类型包含不同的参数数组。

代码语言:javascript
复制
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]数组。但是,当前上下文是Web套接字应用程序时,此数组将等于[client, data]。此设计决策使您可以访问最终将传递给相应处理程序的任何参数。

绑定过滤器

让我们HttpExceptionFiltercreate()方法联系起来。

cats.controller.ts

JS

代码语言:javascript
复制
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

提示@UseFilters()装饰器是从@nestjs/common包导出。

我们在@UseFilters()这里使用了装饰器。与@Catch()装饰器类似,它可以采用单个过滤器实例,也可以采用逗号分隔的过滤器实例列表。我们创建了

HttpExceptionFilter实例。或者,您可以传递类(而不是实例),将实例化责任留给框架,并启用依赖项注入

cats.controller.ts

JS

代码语言:javascript
复制
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

提示:尽可能使用类而不是实例来应用过滤器。由于Nest可以轻松地在整个模块中重用同一类的实例,因此可以减少内存使用量

在上面的示例中,HttpExceptionFilter仅将应用于单个create()路由处理程序,从而使其成为方法范围的。异常过滤器的作用域可以划分为不同级别:方法作用域,控制器作用域或全局作用域。例如,要将过滤器设置为控制器作用域,您可以执行以下操作:

cats.controller.ts

JS

代码语言:javascript
复制
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

这种结构HttpExceptionFilter为在其中定义的每个路由处理程序设置CatsController。这是控制器范围的异常过滤器的示例。最后一个可用范围是全局范围的异常过滤器。

main.ts

JS

代码语言:javascript
复制
async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

警告useGlobalFilters()方法既不为网关设置过滤器,也不为微服务设置过滤器。

全局过滤器用于整个应用程序,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局过滤器(如上例中所示)不能注入依赖项,因为它们不属于任何模块。为了解决此问题,您可以使用以下构造直接从任何模块设置过滤器:

app.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

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

提示:当使用这种方法对过滤器执行依赖项注入时,请注意,无论采用何种结构的模块,该过滤器实际上都是全局的。应该在哪里做?选择HttpExceptionFilter定义了过滤器(在上面的示例中)的模块。同样,useClass这也不是处理自定义提供程序注册的唯一方法。

捕捉一切

为了捕获每个未处理的异常(无论异常类型如何),请将@Catch()装饰器的参数列表保留为空,例如@Catch()

JS

代码语言:javascript
复制
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    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,
      });
  }
}

在上面的示例中,过滤器将捕获已抛出的每个异常,而不将其自身限制为一组特定类。

继承

通常,您将创建完全自定义的异常过滤器,以满足您的应用程序要求。虽然您希望重用已经实现的核心异常过滤器并根据某些因素覆盖行为,但可能存在用例。

为了将异常处理委托给基本过滤器,您需要扩展BaseExceptionFilter并调用继承的catch()方法。此外,HttpServer必须注入引用并传递给super()调用。

JS

代码语言:javascript
复制
import { Catch, ArgumentsHost, HttpServer } from '@nestjs/common';
import { BaseExceptionFilter, HTTP_SERVER_REF } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  constructor(@Inject(HTTP_SERVER_REF) applicationRef: HttpServer) {
    super(applicationRef);
  }

  catch(exception: any, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}

警告扩展基类的过滤器必须由框架本身实例化(不要使用new关键字手动创建实例@UseFilters())。相反,让框架自动实例化它们。

您可以使用全局过滤器通过注入HttpServer引用来扩展基本过滤器。

代码语言:javascript
复制
async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  const httpRef = app.get(HTTP_SERVER_REF);
  app.useGlobalFilters(new AllExceptionsFilter(httpRef));
  await app.listen(3000);
}
bootstrap();

显然,您应该使用您量身定制的业务逻辑(例如添加各种条件)来增强上述实现。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com