在NestJS中,ArgumentHost和ExecutionContext是两个常用的类,用于处理请求和返回的上下文信息。
ArgumentHost类包含了请求的上下文信息,它提供了一个统一的方式来访问请求参数、头部、查询参数等。可以通过在处理器方法中使用@Args()装饰器来获取ArgumentHost实例,从而获取请求的参数和头部信息。
ExecutionContext类包含了处理器方法的上下文信息,它提供了一系列方法来获取请求的原始对象、路由处理器、控制器实例等。可以通过在拦截器、管道和异常过滤器中使用ExecutionContext类来访问这些上下文信息。
Nest 支持创建 HTTP 服务、WebSocket 服务,基于 TCP 通信的微服务
http 服务可以拿到 request、response 对象,ws 服务没有request、response 对象
但是不同的服务都可能需要Guard、Interceptor、Exception Filter 功能
所以我们可以使用 ArgumentHost 和 ExecutionContext 来实现 让 Guard、Interceptor、Exception Filter 跨多种上下文复用
首先我们创建一个项目:
nest new argument -p npm
创建 filter:
nest g filter test --flat --no-spec
接着我们创建一个异常类 待会会用到:
创建 TestException.ts:
export class TestException {
constructor(public message1: string, public message2: string) {
}
}
修改 test.filter.ts: 在@Catch 装饰器里声明filter 处理该异常
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { TestException } from './TestException';
@Catch(TestException)
export class TestFilter<T> implements ExceptionFilter {
catch(exception: TestException, host: ArgumentsHost) {
console.log(exception, host);
}
}
在Controller 里使用:
我们在接口里面抛出了一个 TestException 类型的异常
接着我们访问 http://localhost:3000/ 可以看到控制台打印出了一些信息
可以看到 打印出的信息就是 test.filter.ts 里的console.log 打印出来的信息
第一个圈住的数据就是 exception
后面的数据就是 host
host.getArgs 方法就是取出当前上下文的 reqeust、response、next 参数
修改 test.filter.ts:
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { TestException } from './TestException';
import { Response } from 'express';
@Catch(TestException)
export class TestFilter<T> implements ExceptionFilter {
catch(exception: TestException, host: ArgumentsHost) {
if (host.getType() === 'http') {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response
.status(500)
.json({
message1: exception.message1,
message2: exception.message2,
});
} else if (host.getType() === 'ws') {
} else if (host.getType() === 'rpc') {
}
}
}
刷新页面,就可以看到 filter 返回的响应
所以 ArgumentHost 是用于切换 http、ws、rpc 等上下文类型的,可以根据上下文类型取到对应的 argument
接着创建一个guard 看看在guard 中是怎么样的
nest g guard test --no-spec --flat
可以看到生成里的context 的类型就是为ExecutionContext
可以看到context 有下面这些方法:
可以看到源码里 ExecutionContext 是 ArgumentHost 的子类 并且扩展了 getClass、getHandler 方法
我们在controller 里修改代码如下:
新增 @UseGuards(TestGuard)
import { Controller, Get, UseFilters, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestFilter } from './test.filter';
import { TestException } from './TestException';
import { TestGuard } from './test.guard';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Get()
@UseFilters(TestFilter)
@UseGuards(TestGuard)
getHello(): string {
throw new TestException('测试', '异常')
return this.appService.getHello();
}
}
接着我们修改 test.guard.ts 的代码
打印一下 getClass、getHandler 方法
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class TestGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log(context.getClass(), context.getHandler());
return true;
}
}
接着访问 http://localhost:3000/ 可以看到控制台打印了
为什么 ExecutionContext 里需要多出这俩方法呢?
因为 Guard、Interceptor 的逻辑可能要根据目标 class、handler 有没有某些装饰而决定怎么处理
.举个例子:
先创建一个role.ts
export enum Role {
Admin = 'admin',
User = 'user',
Guest = 'guest'
}
接着定义一个装饰器 role.decorator.ts 作用是往修饰的目标上添加 roles 的 metadata
import { SetMetadata } from "@nestjs/common";
import { Role } from "./role";
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles)
我们去controller里进行使用: 给这个 handler 添加了一个 roles 为 admin 的metadata。
import { Controller, Get, UseFilters, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestFilter } from './test.filter';
import { TestException } from './TestException';
import { TestGuard } from './test.guard';
import { Roles } from './role.decorator';
import { Role } from './role';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Get()
@UseFilters(TestFilter)
@UseGuards(TestGuard)
@Roles(Role.User)
getHello(): string {
throw new TestException('测试', '异常')
return this.appService.getHello();
}
}
最后在 Guard 里 可以根据 metadata 决定是否放行:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { Role } from './role';
/**
* `TestGuard` 类实现了 NestJS 的 `CanActivate` 接口,用于授权检查。
* 它通过检查请求处理器上标注的权限角色,来决定当前请求是否被允许激活。
*/
@Injectable()
export class TestGuard implements CanActivate {
/**
* 构造函数初始化 `TestGuard`。
* @param reflector `Reflector` 实例,用于获取请求处理器上的元数据。
*/
constructor(private reflector: Reflector) {}
/**
* `canActivate` 方法决定当前请求是否应该被激活。
* 它首先检查请求处理器是否标注了需要特定角色的元数据。
* 如果没有标注任何角色要求,则认为请求是激活的。
* 如果标注了角色要求,则检查请求中的用户是否拥有这些角色。
* @param context `ExecutionContext` 实例,用于获取当前的执行上下文,包括请求和处理程序信息。
* @returns `boolean` 或 `Promise<boolean>` 或 `Observable<boolean>`,表示请求是否应该被激活。
*/
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// 从请求处理器上获取需要的角色列表
const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
// 如果没有指定角色要求,则自动激活请求
if (!requiredRoles) {
return true;
}
// 切换到HTTP上下文并获取请求对象
const { user } = context.switchToHttp().getRequest();
// 检查用户角色是否包含在所需角色列表中
return requiredRoles.some((role) => user && user.roles?.includes(role));
}
}
接着我们访问 http://localhost:3000/
说明 Guard 生效 就是 Guard 里的 ExecutionContext 参数的用法
interceptor 也是同样的,我们创建一个interceptor:
nest g interceptor test --no-spec --flat
可以看到也是 ExecutionContext 类型