夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
      • filter 捕获异常
        • filter 参数一:异常对象
        • filter 参数二:host 方法
        • host.getArgs
        • host.getArgByIndex
        • host.switchToHttp
        • 不同上下文判断
      • guard 守卫
        • 可用方法
        • 继承 host 方法
        • 拓展 getClass 和 getHandler 方法
        • 示例:Guard 验证用户权限
      • interceptor 拦截
      • 总结
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

ExecutionContext切换上下文

Nest 支持创建多种类型的服务:

包括 HTTP 服务、WebSocket 服务,还有基于 TCP 通信的微服务。

这三种服务都会支持 Guard、Interceptor、Exception Filter 功能。

那么问题来了:

不同类型的服务它能拿到的参数是不同的,比如 http 服务可以拿到 request、response 对象,而 websocket 服务就没有。

也就是说你不能在 Guard、Interceptor、Exception Filter 里直接操作 request、response,不然就没法复用了,因为 websocket 没有这些。

那如何让同一个 Guard、Interceptor、Exception Filter 在不同类型的服务里复用呢?

Nest 的解决方法是 ArgumentHost 这个类。

我们来看一下:

创建个项目:

nest new argument-host
1

然后创建一个 filter:

nest g filter aaa --flat --no-spec
1

Nest 会 catch 所有未捕获异常,如果是 Exception Filter 声明的异常,那就会调用 filter 来处理。

# filter 捕获异常

那 filter 怎么声明捕获什么异常的呢?

我们创建一个自定义的异常类:

export class AaaException {
    constructor(public aaa: string, public bbb: string) {
        
    }
}
1
2
3
4
5

在 @Catch 装饰器里声明这个 filter 处理该异常:

然后需要启用它:

路由级别启用 AaaFilter,并且在 handler 里抛了一个 AaaException 类型的异常。

也可以全局启用:

app.useGlobalFilters(new AaaFilter());
1

访问 http://localhost:3000 (opens new window) 就可以看到 filter 被调用了。

# filter 参数一:异常对象

filter 的第一个参数就是异常对象,那第二个参数呢?

可以看到,它有这些方法:

# filter 参数二:host 方法

我们用调试的方式跑一下:

点击 create launch.json file 创建一个调试配置文件:

在 .vscode/launch.json 添加这样的调试配置:

{
    "type": "pwa-node",
    "request": "launch",
    "name": "debug nest",
    "runtimeExecutable": "npm",
    "args": [
        "run",
        "start:dev",
    ],
    "skipFiles": [
        "<node_internals>/**"
    ],
    "console": "integratedTerminal",
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

点击调试启动:

打个断点:

浏览器访问 http://localhost:3000 (opens new window) 就可以看到它断住了:

我们分别调用下这些方法试试:

在 debug console 输入 host,可以看到它有这些属性方法:

# host.getArgs

host.getArgs 方法就是取出当前上下文的 reqeust、response、next 参数。

因为当前上下文是 http 服务,如果是 WebSocket 服务,这里拿到的就是别的东西了。

# host.getArgByIndex

host.getArgByIndex 方法是根据下标取参数:

当然,一般不会根据 index 来取,而且这样用:

# host.switchToHttp

调用 switchToHttp 切换到 http 上下文,然后再调用 getRequest、getResponse 方法。

# 不同上下文判断

如果是 websocket、基于 tcp 的微服务等上下文,就分别调用 host.swtichToWs、host.switchToRpc 方法。

这样,就可以在 filter 里处理多个上下文的逻辑,跨上下文复用 filter了。

加个 if else 判断即可:

import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { Response } from 'express';
import { AaaException } from './AaaException';

@Catch(AaaException)
export class AaaFilter implements ExceptionFilter {
  catch(exception: AaaException, host: ArgumentsHost) {
    if(host.getType() === 'http') {
      const ctx = host.switchToHttp();
      const response = ctx.getResponse<Response>();
      const request = ctx.getRequest<Request>();

      response
        .status(500)
        .json({
          aaa: exception.aaa,
          bbb: exception.bbb
        });
    } else if(host.getType() === 'ws') {

    } else if(host.getType() === 'rpc') {

    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

刷新页面,就可以看到 filter 返回的响应:

所以说,ArgumentHost 是用于切换 http、websocket、rpc 等上下文类型的,可以根据上下文类型取到对应的 argument,让 Exception Filter 等在不同的上下文中复用。

那 guard 和 interceptor 里呢?

我们创建个 guard 试一下:

nest g guard aaa --no-spec --flat
1

# guard 守卫

可以看到它传入的是 ExecutionContext:

# 可用方法

有这些方法:

是不是很眼熟?

# 继承 host 方法

没错,ExecutionContext 是 ArgumentHost 的子类,扩展了 getClass、getHandler 方法。

多加这两个方法是干啥的呢?

# 拓展 getClass 和 getHandler 方法

我们调试下看看:

路由级别启用 Guard:

在 Guard 里打个断点:

调用下 context.getClass 和 getHandler:

会发现这俩分别是要调用的 controller 的 class 以及要调用的方法。

为什么 ExecutionContext 里需要拿到目标 class 和 handler 呢?

因为 Guard、Interceptor 的逻辑可能要根据目标 class、handler 有没有某些装饰而决定怎么处理。

# 示例:Guard 验证用户权限

比如权限验证的时候,我们会先定义几个角色:

然后定义这样一个装饰器:

它的作用是往修饰的目标上添加 roles 的 metadata。

然后在 handler 上添加这个装饰器,参数为 admin,也就是给这个 handler 添加了一个 roles 为 admin 的metadata。

这样在 Guard 里就可以根据这个 metadata 决定是否放行了:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { Role } from './role';

@Injectable()
export class AaaGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {

    const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user && user.roles?.includes(role));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

我们在 Guard 里通过 ExecutionContext 的 getHandler 方法拿到了目标 handler 方法。

然后通过 reflector 的 api 拿到它的 metadata。

判断如果没有 roles 的 metadata 就是需要权限,那就直接放行。

如果有,就是需要权限,从 user 的 roles中判断下又没有当前 roles,有的话就放行。

刷新页面,可以看到返回的是 403:

这说明 Guard 生效了。

这就是 Guard 里的 ExecutionContext 参数的用法。

# interceptor 拦截

同样,在 interceptor 里也有这个:

nest g interceptor aaa --no-spec --flat
1

同样可以通过 reflector 取出 class 或者 handler 上的 metdadata。

案例代码在小册仓库 (opens new window)。

# 总结

为了让 Filter、Guard、Exception Filter 支持 http、ws、rpc 等场景下复用,Nest 设计了 ArgumentHost 和 ExecutionContext 类。

ArgumentHost 可以通过 getArgs 或者 getArgByIndex 拿到上下文参数,比如 request、response、next 等。

更推荐的方式是根据 getType 的结果分别 switchToHttp、switchToWs、swtichToRpc,然后再取对应的 argument。

而 ExecutionContext 还提供 getClass、getHandler 方法,可以结合 reflector 来取出其中的 metadata。

在写 Filter、Guard、Exception Filter 的时候,是需要用到这些 api 的。

编辑 (opens new window)
上次更新: 2025/5/14 16:47:16
Metadata和Reflector
Module和Provider的循环依赖处理

← Metadata和Reflector Module和Provider的循环依赖处理→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式