Nest带有一个内置的异常层,该层负责处理整个应用程序中所有未处理的异常。当您的应用程序代码未处理异常时,该层将捕获该异常,然后自动发送适当的用户友好响应。
Nest提供的异常处理是开箱即用的,此操作由全局异常过滤器处理,该过滤器只能处理HttpException(及其子类)的异常,如果无法识别异常(既不HttpException是继承自的类也不是HttpException),则内置异常过滤器会生成以下默认JSON响应:
{
"statusCode" :500 ,
"message" :"内部服务器错误"
}
Nest提供了一个内置HttpException类,从@nestjs/common包中公开。对于典型的基于HTTP REST / GraphQL API的应用程序,最佳实践是在发生某些错误情况时发送标准HTTP响应对象。
HttpException构造函数接收两个参数,第一个参数是JSON格式的响应主体,可以是字符串,也可以是对象;第二个参数是Http状态码。默认情况JSON响应主体包含两个属性响应状态码和响应信息,如果仅要覆盖JSON响应主体的消息部分, 则在response参数提供一个字符串,要覆盖整个JSON响应主体,请在response参数中传递一个对象。Nest将序列化对象,并将其作为JSON响应正文返回。
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
import { AppService } from './app.service';
/**
* @Controller可以接收一个参数用于区分路由分组
*/
@Controller('/app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/hello')
getHello(): string {
/**
* 故意抛出一个非HttpException(及其子类)的异常,Nest使用全局异常过滤器对其处理,
* 默认以json格式响应以下信息:
* {
"statusCode": 500,
"message": "Internal server error"
}
*/
throw new Error('手动抛出一个异常....');
return this.appService.getHello();
}
/**
* 抛出HttpException异常,此异常从@nestjs/common包中导入,HttpException构造函数接收
* 两个参数,第一个参数是JSON响应主体(response),可以是字符串也可以是对象,
* 第二个参数是HTTP状态码。
*
* 以下例子中当访问 localhost:3000/app/findUser 就会响应如下信息:
* {
* "statusCode": 403,
* "message": "禁止访问"
* }
* 默认情况JSON响应主体包含两个属性响应状态码和响应信息,如果仅要覆盖JSON响应主体的消息部分,
* 则在response参数提供一个字符串,要覆盖整个JSON响应主体,
* 请在response参数中传递一个对象。Nest将序列化对象,并将其作为JSON响应正文返回。
*/
@Get('/findUser')
findUser() {
throw new HttpException(
{
status: HttpStatus.FORBIDDEN,
error: '这是自定义异常信息',
},
HttpStatus.FORBIDDEN,
);
}
}
1.自定义异常
自定义异常非常简单,只需要继承HttpException类即可,然后调用HttpException类的构造函数。
import { HttpException, HttpStatus } from '@nestjs/common';
/**
* 自定义异常过滤器,需要继承HttpException
*/
export class ForbiddenException extends HttpException {
constructor(message?: string) {
//调用父类构造
super(message || '禁止访问', HttpStatus.FORBIDDEN);
}
}
import { ForbiddenException } from './common/exception/ForbiddenException';
/**
* 测试自定义异常,响应信息为:
{
"statusCode": 403,
"message": "禁止访问"
}
*/
@Get('/testCustomException')
testCustomException() {
throw new ForbiddenException();
}
Nest提供了一组从base继承的标准异常HttpException。这些是从@nestjs/common程序包中公开的,它们代表许多最常见的HTTP异常:
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- HttpVersionNotSupportedException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableEntityException
- InternalServerErrorException
- NotImplementedException
- ImATeapotException
- MethodNotAllowedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
- PreconditionFailedException
2.异常过滤器
虽然基本(内置)异常过滤器可以为您自动处理许多情况,但是我们希望能完全控制异常层,例如通过日志记录异常信息或基于一个动态条件使用其他JSON模式,异常过滤器正是为此目的而设计的。它们使您可以控制精确的控制流程以及发送回客户端的响应内容。
实现异常过滤器就两步:
(1).实现ExceptionFilter接口,并重写catch(exception: T, host: ArgumentsHost)方法,其中T表示捕获异常的类型,exception表示当前正在处理的异常对象,host是一个ArgumentsHost对象,ArgumentsHost是一个功能强大的实用工具对象,这里通过它来获取执行上下文,从而得到请求对象和响应对象。
(2).使用@Catch装饰器装饰要处理的异常类型。例如@Catch(HttpException)这会告诉Nest,这个特殊的过滤器正在寻找异常类型为HttpException的异常,@Catch()装饰可以采用单个参数或逗号分隔的列表。例如@Catch(HttpException,ForbiddenException)。一般为了捕获所有异常(无论异常类型是什么),请将@Catch()的参数列表设置为空,例如@Catch(),过滤器将捕获抛出的每个异常,无论其类型如何。 ```typescript // src/common/exception/HttpExceptionFilter.ts import { ExceptionFilter, ArgumentsHost, HttpException, Catch } from ‘@nestjs/common’; import { Request, Response } from ‘express’; import { HttpArgumentsHost } from ‘@nestjs/common/interfaces’;
@Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { /*
- @param exception 为捕获的异常类型,exception:HttpException表示捕获HttpException异常
- @param host
*/
catch(exception: HttpException, host: ArgumentsHost) {
//获取上下文
const ctx: HttpArgumentsHost = 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, }); } }
使用自定义异常过滤器需要借助@UseFilters()装饰器,@UseFilters()装饰器由@nestjs/common包提供,@UseFilters()可以接收一个或多个过滤器类或过滤器类的实例(对过滤器进行了new操作),Nest官方推荐使用过滤器类的方式,解释是:由于Nest可以轻松地在整个模块中重用同一类的实例,因此可以减少内存使用。
```typescript
import {
Controller,
Get,
HttpException,
HttpStatus,
UseFilters,
} from '@nestjs/common';
import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
@Controller('/app')
export class AppController {
constructor(private readonly appService: AppService) {}
/**
* 测试自定义异常
*/
@Get('/testCustomException')
testCustomException() {
throw new ForbiddenException();
}
/**
* 测试自定义异常过滤器
*/
@Get('/testCustomExceptionFilter')
@UseFilters(new HttpExceptionFilter())
testCustomExceptionFilter() {
throw new ForbiddenException();
}
}
3.异常过滤器的作用域
异常过滤器的作用域三种,分别是路由方法作用域、控制器作用域、全局作用域,由于上面例子中@UseFilters()放在控制器的路由方法上,所以只会对该路由方法有效,对于其他路由方法是无效的。如果将@UseFilters()放在Controller上则表示会对当前Controller所有的方法进行异常过滤处理。如果将异常过滤器注册到全局则会对整个应用进行异常处理。
import {
Controller,
Get,
HttpException,
HttpStatus,
UseFilters,
} from '@nestjs/common';
import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
@Controller('/app')
@UseFilters(HttpExceptionFilter) // 控制器作用域,能对当前控制器发生异常进行异常处理
export class AppController {
constructor(private readonly appService: AppService) {}
/**
* 测试自定义异常
*/
@Get('/testCustomException')
testCustomException() {
throw new ForbiddenException();
}
/**
* 测试自定义异常过滤器
*/
@Get('/testCustomExceptionFilter')
@UseFilters(HttpExceptionFilter) // 路由作用域,只能针对当前方法进行异常处理
testCustomExceptionFilter() {
throw new ForbiddenException();
}
}
//main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//使用全局异常过滤器,网关或混合应用程序设置过滤器
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
4.对外提供异常过滤器
就依赖关系注入而言,从任何模块外部注册的全局过滤器(useGlobalFilters()如上例所示)都不能注入依赖关系,因为这是在任何模块的上下文之外完成的。为了解决此问题,您可以使用以下结构直接从任何模块中注册全局范围的过滤器。
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HttpExceptionFilter } from './common/exception/HttpExceptionFilter';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
AppService,
],
})
export class AppModule {}
5.继承
如果想扩展内置的默认全局异常过滤器并根据某些因素覆盖行为时,可以实现BaseExceptionFilter接口并重写catch(),BaseExceptionFilter由@nestjs/core包提供。
import { BaseExceptionFilter } from '@nestjs/core';
import { Catch, ArgumentsHost } from '@nestjs/common';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost): void {
super.catch(exception, host);
}
}
全局过滤器可以扩展基本过滤器。这可以通过两种方式之一来完成。第一种方法是HttpServer在实例化自定义全局过滤器时注入引用:
//方式1
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
令牌: