Nestjs实战干货-概况-异常过滤器-Exception filters

news2024/11/13 12:06:07

异常过滤器

Nest 带有一个内置的异常层,负责处理应用程序中所有未处理的异常。当应用程序代码未处理异常时,该层会捕获该异常,然后自动发送适当的用户友好响应。

来自静态目录的图像

开箱即用,此操作由内置的全局异常过滤器执行,该过滤器处理类型为HttpException(及其子类)的异常。当异常无法识别(既不是 HttpException 也不是从HttpException 继承的类)时,内置异常过滤器会生成以下默认 JSON 响应:

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

全局异常过滤器部分支持 http-errors 库。基本上,任何包含 statusCodemessage 属性的抛出异常都将正确填充并作为响应发送返回(而不是默认的InternalServerErrorException 对于无法识别的异常)。

抛出标准异常

Nest 提供了一个内置的HttpException类,来自@nestjs/common包。对于典型的基于 HTTP REST/GraphQL API 的应用程序,当某些错误条件发生时,最好的做法是发送标准的 HTTP 响应对象。

例如,在 CatsController 中,我们有一个findAll()方法( GET 路由处理程序)。假设此路由处理程序出于某种原因引发异常。为了演示这一点,我们将按如下方式对其进行硬编码:

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

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

当客户端调用接口的时候,返回的信息如下:

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

HttpException 构造函数有两个必要的参数来决定响应:

  • response 参数定义 JSON 响应体。它可以是 stringobject,如下所述。

  • status参数定义HTTP状态代码。

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

  • statusCode:默认为 status 参数中提供的 HTTP 状态代码

  • message:基于状态的 HTTP 错误的简短描述

仅覆盖 JSON 响应主体的消息部分,请在 response参数中提供一个 string

要覆盖整个 JSON 响应主体,请在response 参数中传递一个objectNest将序列化对象,并将其作为JSON 响应返回。

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

第三个构造函数参数(可选) - options - 可用于提供错误cause。此cause对象不会序列化为响应对象,但它可用于日志记录目的,提供有关导致引发HttpException的内部错误的有价值的信息。

这是一个覆盖整个响应正文的示例:

@Get()
async findAll() {
  try {
    await this.service.findAll()
  } catch (error) {
    throw new HttpException({
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
    }, HttpStatus.FORBIDDEN, {
      cause: error
    });
  }
}

返回响应结果如下:

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

自定义异常

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

export class ForbiddenException extends HttpException {
  constructor() {
    super("Forbidden", HttpStatus.FORBIDDEN);
  }
}

由于ForbiddenException扩展了基础HttpException,它将与内置的异常处理程序无缝协作,因此我们可以在findAll()方法中使用它。

@Get()
async findAll() {
  throw new ForbiddenException();
}

内置 HTTP 异常

Nest 提供了一组标准异常,这些异常继承自基础 ‘HttpException’。这些是从“@nestjs/common”包中公开的,并且代表许多最常见的 HTTP 异常:

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

所有内置异常还可以使用options参数提供错误原因和错误描述:

throw new BadRequestException("Something bad happened", {
  cause: new Error(),
  description: "Some error description",
});

使用上述代码,返回的响应体如下:

{
  "message": "Something bad happened",
  "error": "Some error description",
  "statusCode": 400
}

异常过滤器 filter

虽然内置异常过滤器可以自动为你处理许多情况,但你可能希望对异常层进行完全控制。例如,你可能希望添加日志记录或根据某些动态因素使用不同的 JSON 架构。异常过滤器正是为此目的而设计的。它允许你控制确切的控制流以及发送回客户端的响应内容。

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

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

所有异常过滤器都应实现通用的ExceptionFilter<T>接口。这要求您提供catch(exception: T, host: ArgumentsHost) 方法及其指示的签名。“T”表示异常的类型。

如果您使用的是@nestjs/platform-fastify,则可以使用response.send()而不是response.json()。不要忘记从fastify导入正确的类型。

@Catch(HttpException)装饰器将所需的元数据绑定到异常过滤器,告诉 Nest 此特定过滤器正在查找类型为HttpException的异常,而不是其他任何异常。@Catch()装饰器可以采用单个参数或逗号分隔的列表。这使您可以一次为多种类型的异常设置过滤器。

参数 host

让我们看看 catch() 方法的参数。exception 参数是当前正在处理的异常对象。host 参数是一个ArgumentsHost 对象。ArgumentsHost是一个功能强大的实用程序对象,我们将在 [执行上下文章节]中进一步研究它。在此代码示例中,我们使用它来获取对传递给原始请求处理程序(在异常发生控制器中的)的RequestResponse对象的引用。在此代码示例中,我们在ArgumentsHost上使用了一些帮助程序方法来获取所需的RequestResponse对象。了解更多关于ArgumentsHost [这里]。

这种抽象级别的原因是ArgumentsHost在所有上下文中都起作用(例如,我们现在正在使用的 HTTP 服务器上下文,以及微服务和 WebSockets)。在执行上下文章节中,我们将看到如何使用ArgumentsHost及其帮助程序函数的强大功能访问执行上下文的适当基础参数。这将允许我们编写在所有上下文中运行的通用异常过滤器。

绑定过滤器

让我们将 HttpExceptionFilter 绑定到 CatsControllercreate() 方法上。

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

@UseFilters()装饰器,来自包@nestjs/common

我们在这里使用了@UseFilters()装饰器。与@Catch()装饰器类似,它可以采用单个过滤器实例或逗号分隔的过滤器实例列表。在这里,我们就地创建了HttpExceptionFilter的实例。或者,您可以传递类(而不是实例),将实例化的责任留给框架,并启用 依赖关系注入

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

如果可能,最好使用类而不是实例来应用过滤器。它减少了内存使用量,因为 Nest 可以轻松地在整个模块中重用同一类的实例。

在上面的示例中,HttpExceptionFilter仅应用于单个create()方法上的,使其成为方法范围。异常过滤器的作用域可以位于不同的级别:方法控制器全局。例如,若要将过滤器设置为控制器范围,请执行以下操作:

@UseFilters(new HttpExceptionFilter())
export class CatsController {}

此构造为CatsController中定义的每个路由处理程序设置HttpExceptionFilter

若要创建全局范围的过滤器,请执行以下操作:

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

useGlobalFilters()方法不会为网关或混合应用程序设置过滤器。

全局范围的过滤器用于整个应用程序,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局过滤器(如上例所示使用 useGlobalFilters() )无法注入依赖关系,因为这是在任何模块的上下文之外完成的。为了解决此问题,您可以使用以下结构直接从任何模块注册一个全局范围的过滤器:

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

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

当使用这种方法为过滤器执行依赖注入时,请注意,无论采用这种构造的模块如何,过滤器实际上都是全局的。这应该在哪里完成?选择定义过滤器(HttpExceptionFilter 在上面的示例中)的模块。此外,useClass 这不是处理自定义提供程序注册的唯一方法。在这里了解更多。

您可以根据需要使用此技术添加任意数量的过滤器;只需将每个添加到提供程序数组即可。

抓住一切

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

在下面的示例中,我们有一个与平台无关的代码,因为它使用 HTTP 适配器来传递响应,并且不直接使用任何特定于平台的对象(Request 和 Response):

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

将捕获所有内容的异常过滤器与绑定到特定类型的过滤器组合时,应首先声明“捕获任何内容”过滤器,以允许特定过滤器正确处理绑定类型。

继承

通常,您将创建完全定制的异常过滤器来满足您的应用程序需求。但是,当您希望简单地扩展内置的默认全局异常过滤器并根据某些因素覆盖行为时,可能会有一些用例。

为了将异常处理委托给基本过滤器,您需要扩展BaseExceptionFilter并调用继承的catch()方法。

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

扩展的方法作用域和控制器作用域的过滤器 BaseExceptionFilter 不应使用实例化 new。相反,让框架自动实例化它们。

上面的实现只是一个演示方法的 shell。您对扩展异常过滤器的实现将包括您定制的业务逻辑(例如,处理各种条件)。

全局过滤器可以扩展基本过滤器。这可以通过两种方式之一完成。

第一种方法是 HttpAdapter 在实例化自定义全局过滤器时注入引用:

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 的令牌。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三、Locust任务(task)详解

当一个负载测试开始时&#xff0c;将为每个模拟用户创建一个用户类的实例&#xff0c;他们将在自己的绿色线程中开始运行。当这些用户运行时&#xff0c;他们会选择执行的任务&#xff0c;睡眠一段时间&#xff0c;然后选择一个新的任务&#xff0c;如此循环。 这些任务是正常…

二、Java 并发编程(4)

本章概要 Java 中的锁 乐观锁悲观锁自旋锁synchronizedReentrantLocksynchronized 与 ReentrantLock 对比SemaphoreAtomicInteger可重入锁公平锁和非公平锁读写锁共享锁和独占锁重量级锁和轻量级锁偏向锁分段锁同步锁和死锁如何进行锁优化 2.6 Java 中的锁 Java 中的锁主要…

【C语言进阶:动态内存管理】C/C++中程序内存区域的划分

⚡C/C中程序内存区域的划分 C/C程序内存分配的几个区域&#xff1a; 栈区&#xff08;stack&#xff09;&#xff1a;在执行函数时&#xff0c;函数内局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指…

day8 互斥锁/读写锁的概念及使用、死锁的避免

目录 互斥锁的概念和使用 线程通信 - 互斥 互斥锁的创建和销毁 互斥锁的创建 互斥锁的销毁 互斥锁的使用 申请锁 释放锁 互斥锁的概念和使用 线程通信 - 互斥 临界资源&#xff1a; 一次只允许一个任务&#xff08;进程、线程&#xff09;访问的共享资源&#xff1b…

Maven-依赖管理

一. 依赖管理 1. maven-依赖管理-依赖配置 依赖&#xff1a;指当前项目运行所需要的jar包。一个项目中可以引入多个依赖&#xff1a; 例如&#xff1a;在当前工程中&#xff0c;我们需要用到logback来记录日志&#xff0c;此时就可以在maven工程的pom.xml文件中&#xff0c…

Python让ChatGPT全自动改写生成文章教程

ChatGPT是一个在自然语言处理领域非常先进的文本生成模型&#xff0c;它能够产生高质量、连贯的文章。它受到了广泛的关注&#xff0c;因为它可以自动生成大量的文本&#xff0c;从而减轻了人工写作的负担。怎么使用chatgpt批量改写文章&#xff1f;最简单的方式就是找到一家接…

I.MX6U开发板使用OTG烧写系统

1.系统烧写 在实际的产品开发中肯定不可能通过网络来运行&#xff0c;否则没网的时候产品岂不 是就歇菜了。因此我们需要将 uboot、linux kernel、.dtb(设备树)和 rootfs 这四个文件烧写到板子 上的 EMMC、NAND 或 QSPI Flash 等其他存储设备上&#xff0c;这样不管有没有网络我…

SpringCloud 使用sentinel

一、添加依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 二、配置文件配置地址 spring:cloud:sentinel:transport:dashboard: localhost:8080三…

机器学习:基于逻辑回归对优惠券使用情况预测分析

机器学习&#xff1a;基于逻辑回归对优惠券使用情况预测分析 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x…

Elasticsearch:集群管理的一些建议

在之前的文章 “Elasticsearch&#xff1a;集群管理” &#xff0c;我们对集群管理做了一些介绍。在今天的文章中&#xff0c;我们接着来聊一下有关配置的方面的问题。这在很大程度上取决于你的用例&#xff0c;是索引还是搜索繁重。 我们将在这里讨论在集群设置方面我们需要关…

中国算力的想象力有多大?|产业特稿

巨头入场和“东数西算”的助推&#xff0c;让中国离这个万亿级算力蓝海更近了一步。 作者|思杭 编辑|皮爷 出品|产业家 2023年初&#xff0c;在青岛、济南、日照等12座城市&#xff0c;一座座崭新的大型数据中心拔地而起。 其中&#xff0c;最引人瞩目的属2月23日&#xff…

文件上传漏洞 --- php邂逅windows通用上传缺陷

目录 后端源码 前端源码 后端代码审计 方式一绕过原理 --- 冒号加特性 验证及结果 方式二绕过原理 --- 数据流 验证及结果 环境需求 php5.2.17IIS环境&#xff0c;可以下载phpstuday2018来满足环境的要求。 后端源码 <?php //U-Mail demo ... if(isset($_POST[sub…

【AI能否取代设计师】「Stable Diffusion」AI绘画黑科技将告诉你答案

上一篇文章&#xff1a;【AI绘画】我以Midjourney为主学习AI绘画效果咋样&#xff1f;_山楂山楂丸的博客-CSDN博客 目录 前言 一、「Stable Diffusion」 是什么 二、「Stable Diffusion」上手演练 三、竟然还有ChatGPT&#xff1f; 四、「Stable Diffusion」作品展示 五、…

爆火的Auto-GPT:实战及运行体验

Auto-GPT可以说是目前AI应用方向最火爆的项目了&#xff0c;自从3月份上线以来&#xff0c;一个月疯狂拦下将近7万star&#xff08;截至本文写稿时69.5k&#xff09;。它的目的是探索诸如GPT-4这样的大语言模型自主完成任务的能力。业界也有一些大佬出来表示这个项目真的很有趣…

pnpm与monorepo架构

本文根据b站up小满zs教程记录 软硬连接介绍 在cmd环境下输入mklink 创建链接需要开启管理员模式 软连接 # mklink 要创建的连接名称 源文件 mklink ruan.js ying.js硬链接 共享一个内存地址。 # mklink 要创建的连接名称 源文件 mklink /H ying.js ying.jspn…

工程项目管理系统源码-简洁+好用+全面-工程项目管理

​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 ​系统定义 工程项目管理企业不直接与该工程项目的总承包企…

STL : 单向链表 Forward_list 与 双向链表 List

目录 Forward_list Member functions List Elements access Modifiers Operations Observes Forward_list #include using namespace std; 单链表&#xff1a;数据的存储位置是分散的、随机的&#xff0c;整个链表的数据的线性关系通过指针来维持&#xff1b; 擅长…

【LeetCode 训练营 3,5】无重复字符的最长子串+最长回文子串

&#x1f48c; 博客内容&#xff1a;LeetCode 训练营 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#xff1a;这…

架构重构的技巧

1 代码重构 定义 对软件代码做任何改动以增加可读性或者简化结构而不影响输出结果。 目的 增加可读性、增加可维护性、可扩展性 3 关键点 不影响输出不修正错误不增加新的功能性 代码重构时&#xff0c;发现有个功能实现逻辑不合理&#xff0c;可直接修改吗&#xff1f;…

网络安全-JDBC反序列化漏洞与RCE

目录环境Black Hat Europe 2019漏洞原理攻击手法mysql-connector-java的分析Mysql协议抓包分析Mysql服务器docker启动抓包&&分析Fake Mysql Server搭建Java反序列化工具ysoserial使用fnmsd师傅的MySQL_Fake_Server使用Y4tacker师傅的脚本JDBC代码项目架构代码复现参考环…