# 異常過濾器
Nest 帶有一個內置的**異常層**,負責處理應用程序中所有未處理的異常。當您的應用程序代碼未處理異常時,該層將捕獲該異常,然后自動發送適當的用戶友好響應。

開箱即用,此操作由內置的全局異常過濾器執行,該過濾器處理類型 `HttpException`(及其子類)的異常。每個發生的異常都由全局異常過濾器處理, 當這個異常**無法被識別**時 (既不是 `HttpException` 也不是繼承的類 `HttpException` ) , 用戶將收到以下 `JSON` 響應:
```json
{
"statusCode": 500,
"message": "Internal server error"
}
```
> **提示**:全局異常過濾器部分支持該`http-errors`庫。基本上,任何包含`statusCode`and`message`屬性的拋出異常都將被正確填充并作為響應發送回(而不是默認`InternalServerErrorException`的無法識別的異常)。
>
## 基礎異常類
`Nest`提供了一個內置的 `HttpException` 類,它從 `@nestjs/common` 包中導入。對于典型的基于`HTTP` `REST/GraphQL` `API`的應用程序,最佳實踐是在發生某些錯誤情況時發送標準HTTP響應對象。
在 `CatsController`,我們有一個 `findAll()` 方法(`GET` 路由)。假設此路由處理程序由于某種原因引發異常。 為了說明這一點,我們將對其進行如下硬編碼:
> cats.controller.ts
```typescript
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
```
> 我們在這里使用了 `HttpStatus` 。它是從 `@nestjs/common` 包導入的輔助枚舉器。
現在當客戶端調用這個端點時,響應如下所示:
```json
{
"statusCode": 403,
"message": "Forbidden"
}
```
`HttpException` 構造函數有兩個必要的參數來決定響應:
- `response` 參數定義 `JSON` 響應體。它可以是 `string` 或 `object`,如下所述。
- `status`參數定義`HTTP`[狀態代碼](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)。
默認情況下,`JSON` 響應主體包含兩個屬性:
- `statusCode`:默認為 `status` 參數中提供的 `HTTP` 狀態代碼
- `message`:基于狀態的 `HTTP` 錯誤的簡短描述
僅覆蓋 `JSON` 響應主體的消息部分,請在 `response`參數中提供一個 `string`。
要覆蓋整個 `JSON` 響應主體,請在`response` 參數中傳遞一個`object`。 `Nest`將序列化對象,并將其作為`JSON` 響應返回。
第二個構造函數參數-`status`-是有效的 `HTTP` 狀態代碼。 最佳實踐是使用從`@nestjs/common`導入的 `HttpStatus`枚舉。
這是一個覆蓋整個響應正文的示例:
> cats.controller.ts
```typescript
@Get()
async findAll() {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
}, HttpStatus.FORBIDDEN);
}
```
使用上面的代碼,響應如下所示:
```json
{
"status": 403,
"error": "This is a custom message"
}
```
## 自定義異常
在許多情況下,您無需編寫自定義異常,而可以使用內置的 `Nest HTTP`異常,如下一節所述。 如果確實需要創建自定義的異常,則最好創建自己的**異常層次結構**,其中自定義異常繼承自 `HttpException` 基類。 使用這種方法,`Nest`可以識別您的異常,并自動處理錯誤響應。 讓我們實現這樣一個自定義異常:
> forbidden.exception.ts
```typescript
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
```
由于 `ForbiddenException` 擴展了基礎 `HttpException`,它將和核心異常處理程序一起工作,因此我們可以在 `findAll()`方法中使用它。
> cats.controller.ts
```typescript
@Get()
async findAll() {
throw new ForbiddenException();
}
```
## 內置HTTP異常
為了減少樣板代碼,Nest 提供了一系列繼承自核心異常 `HttpException` 的可用異常。所有這些都可以在 `@nestjs/common`包中找到:
* `BadRequestException`
* `UnauthorizedException`
* `NotFoundException`
* `ForbiddenException`
* `NotAcceptableException`
* `RequestTimeoutException`
* `ConflictException`
* `GoneException`
* `HttpVersionNotSupportedException`
* `PayloadTooLargeException`
* `UnsupportedMediaTypeException`
* `UnprocessableEntityException`
* `InternalServerErrorException`
* `NotImplementedException`
* `ImATeapotException`
* `MethodNotAllowedException`
* `BadGatewayException`
* `ServiceUnavailableException`
* `GatewayTimeoutException`
* `PreconditionFailedException`
## 異常過濾器
雖然基本(內置)異常過濾器可以為您自動處理許多情況,但有時您可能希望對異常層擁有**完全控制權**,例如,您可能希望基于某些動態因素添加日志記錄或使用不同的 `JSON` 模式。 **異常過濾器**正是為此目的而設計的。 它們使您可以控制精確的控制流以及將響應的內容發送回客戶端。
讓我們創建一個異常過濾器,它負責捕獲作為`HttpException`類實例的異常,并為它們設置自定義響應邏輯。為此,我們需要訪問底層平臺 `Request`和 `Response`。我們將訪問`Request`對象,以便提取原始 `url`并將其包含在日志信息中。我們將使用 `Response.json()`方法,使用 `Response`對象直接控制發送的響應。
> http-exception.filter.ts
```typescript
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` 表示異常的類型。
`@Catch()` 裝飾器綁定所需的元數據到異常過濾器上。它告訴 `Nest`這個特定的過濾器正在尋找 `HttpException` 而不是其他的。在實踐中,`@Catch()` 可以傳遞多個參數,所以你可以通過逗號分隔來為多個類型的異常設置過濾器。
## 參數主機
讓我們看一下該 `catch()` 方法的參數。該 `exception` 參數是當前正在處理的異常對象。該host參數是一個 `ArgumentsHost` 對象。 `ArgumentsHost` 是一個功能強大的實用程序對象,我們將在[應用上下文章節](https://docs.nestjs.com/fundamentals/execution-context) *中進一步進行研究。在此代碼示例中,我們使用它來獲取對 `Request` 和 `Response` 對象的引用,這些對象被傳遞給原始請求處理程序(在異常發生的控制器中)。在此代碼示例中,我們使用了一些輔助方法 `ArgumentsHost` 來獲取所需的 `Request` 和 `Response` 對象。`ArgumentsHost` 在[此處](https://docs.nestjs.com/fundamentals/execution-context)了解更多信息。
之所以如此抽象,是因為它 `ArgumentsHost` 可以在所有上下文中使用(例如,我們現在正在使用的 `HTTP` 服務器上下文,以及微服務和 `WebSocket` )。在應用上下文章節中,我們將看到如何使用 `ArgumentsHost` 及其輔助函數訪問任何應用上下文中相應的底層參數。這將使我們能夠編寫可在所有上下文中運行的通用異常過濾器。
## 綁定過濾器
讓我們將 `HttpExceptionFilter` 綁定到 `CatsController` 的 `create()` 方法上。
> cats.controller.ts
```typescript
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
```
> `@UseFilters()` 裝飾器需要從 `@nestjs/common` 包導入。
我們在這里使用了 `@UseFilters()` 裝飾器。和 `@Catch()`裝飾器類似,它可以使用單個過濾器實例,也可以使用逗號分隔的過濾器實例列表。 我們創建了 `HttpExceptionFilter` 的實例。另一種可用的方式是傳遞類(不是實例),讓框架承擔實例化責任并啟用依賴注入。
> cats.controller.ts
```typescript
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
```
> 盡可能使用類而不是實例。由于 `Nest` 可以輕松地在整個模塊中重復使用同一類的實例,因此可以減少**內存使用**。
在上面的示例中,`HttpExceptionFilter` 僅應用于單個 `create()` 路由處理程序,使其成為方法范圍的。 異常過濾器的作用域可以劃分為不同的級別:方法范圍,控制器范圍或全局范圍。 例如,要將過濾器設置為控制器作用域,您可以執行以下操作:
> cats.controller.ts
```typescript
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
```
此結構為 `CatsController` 中的每個路由處理程序設置 `HttpExceptionFilter`。
要創建一個全局范圍的過濾器,您需要執行以下操作:
> main.ts
```typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
```
> 該 `useGlobalFilters()` 方法不會為網關和混合應用程序設置過濾器。
全局過濾器用于整個應用程序、每個控制器和每個路由處理程序。就依賴注入而言,從任何模塊外部注冊的全局過濾器(使用上面示例中的 `useGlobalFilters()`)不能注入依賴,因為它們不屬于任何模塊。為了解決這個問題,你可以注冊一個全局范圍的過濾器直接為任何模塊設置過濾器:
> app.module.ts
```typescript
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
```
>當使用此方法對過濾器執行依賴注入時,請注意,無論采用哪種結構的模塊,過濾器實際上都是全局的。 應該在哪里做? 選擇定義了過濾器(以上示例中為 `HttpExceptionFilter`)的模塊。 同樣,`useClass`不是處理自定義提供程序注冊的唯一方法。 在[這里](https://docs.nestjs.com/fundamentals/custom-providers)了解更多。
您可以根據需要添加任意數量的過濾器;只需將每個組件添加到 `providers`(提供者)數組。
## 捕獲異常
為了捕獲每一個未處理的異常(不管異常類型如何),將 `@Catch()` 裝飾器的參數列表設為空,例如 `@Catch()`。
在下面的示例中,我們有一個與平臺無關的代碼,因為它使用[HTTP 適配器](https://docs.nestjs.com/faq/http-adapter)來傳遞響應,并且不直接使用任何特定于平臺的對象(`Request`和`Response`):
> any-exception.filter.ts
~~~typescript
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 {
// 在某些情況下,`httpAdapter` 可能在構造方法中不可用,因此我們應該在這里解決它。
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()` 方法。
>all-exceptions.filter.ts
```typescript
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);
}
}
```
> 繼承自基礎類的過濾器必須由框架本身實例化(不要使用 `new` 關鍵字手動創建實例)
上面的實現只是一個演示。擴展異常過濾器的實現將包括定制的業務邏輯(例如,處理各種情況)。
全局過濾器可以擴展基本過濾器。這可以通過兩種方式來實現。
第一種方法是`HttpAdapter`在實例化自定義全局過濾器時注入引用:
```typescript
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` `token`,[如下所示](#綁定過濾器)。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?