## 应用上下文
Nest提供了一些应用类来简化在不同应用上下文之间编写应用(例如Nest HTTP应用,微服务和WebSockets应用)。这些应用可以用于创建通用的守卫,过滤器和拦截器,可以工作在控制器,方法和应用上下文中。
本章包括`ArgumentsHost`和`ExecutionContext`两个类.
## `ArgumentsHost`类
`ArgumentsHost`类提供了获取传递给处理程序的参数。它允许选择合适的上下文(例如HTTP,RPC(微服务)或者Websockets)来从框架中获取参数。框架提供了`ArgumentsHost`的实例,作为`host`参数提供给需要获取的地方。例如,在异常过滤器中传入`ArgumentsHost`参数来调用`catch()`方法。
`ArgumentsHost`简单地抽象为处理程序参数。例如,在HTTP应用中(使用`@nestjs/platform-express`时),host对象封装了Express的`[request, response, next] `数组,`reuest`是一个`request`对象,`response`是一个`response`对象,`next`是控制应用的请求响应循环的函数。此外,在GraphQL应用中,host包含`[root, args, context, info]`数组。
## 当前应用上下文
当构建通用的守卫,过滤器和拦截器时,意味着要跨应用上下文运行,我们需要一种方法来确定我们的方法当前正在运行的应用程序类型。可以使用 `ArgumentsHost`的`getType()`方法。
```typescript
if (host.getType() === 'http') {
// do something that is only important in the context of regular HTTP requests (REST)
} else if (host.getType() === 'rpc') {
// do something that is only important in the context of Microservice requests
} else if (host.getType<GqlContextType>() === 'graphql') {
// do something that is only important in the context of GraphQL requests
}
```
> `GqlContextType`从中`@nestjs/graphql`导入。
有了可用的应用程序类型,我们可以编写更多的通用组件,如下所示。
### `Host`处理程序参数
要获取传递给处理程序的参数数组,使用host对象的`getArgs()`方法。
```typescript
const [req, res, next] = host.getArgs();
```
可以使用`getArgByIndex()`根据索引获取指定参数:
```typescript
const request = host.getArgByIndex(0);
const response = host.getArgByIndex(1);
```
在这些例子中我们通过索引来获取请求响应对象,这并不推荐,因为它将应用和特定上下文耦合。为了使代码鲁棒性更好,更可复用,你可以在程序中使用host对象的应用方法来切换合适的应用上下文,如下所示:
```typescript
/**
* Switch context to RPC.
*/
switchToRpc(): RpcArgumentsHost;
/**
* Switch context to HTTP.
*/
switchToHttp(): HttpArgumentsHost;
/**
* Switch context to WebSockets.
*/
switchToWs(): WsArgumentsHost;
```
使用 `switchToHttp`() 方法重写前面的例子, `host.switchToHttp()`帮助方法调用一个HTTP应用的`HttpArgumentsHost`对象. `HttpArgumentsHost`对象有两个有用的方法,我们可以用来提取期望的对象。我们也可以使用Express类型的断言来返回原生的Express类型对象:
```typescript
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
```
类似地,`WsArgumentsHost`和`RpcArgumentsHost`有返回微服务和WebSockets上下文的方法,以下是`WsArgumentsHost`的方法:
```typescript
export interface WsArgumentsHost {
/**
* Returns the data object.
*/
getData<T>(): T;
/**
* Returns the client object.
*/
getClient<T>(): T;
}
```
以下是`RpcArgumentsHost`的方法:
```typescript
export interface RpcArgumentsHost {
/**
* Returns the data object.
*/
getData<T>(): T;
/**
* Returns the context object.
*/
getContext<T>(): T;
}
```
### 执行上下文类
`ExecutionContext`扩展了`ArgumentsHost`,提供有关当前执行过程的其他详细信息。和`ArgumentsHost`类似,Nest在需要的时候提供了一个`ExecutionContext`的实例, 例如守卫的`canActivate()`方法和拦截器的`intercept()`方法,它提供以下方法:
```typescript
export interface ExecutionContext extends ArgumentsHost {
/**
* Returns the type of the controller class which the current handler belongs to.
*/
getClass<T>(): Type<T>;
/**
* Returns a reference to the handler (method) that will be invoked next in the
* request pipeline.
*/
getHandler(): Function;
}
```
`getHandler()`方法返回要调用的处理程序的引用。`getClass()`方法返回一个特定处理程序所属的控制器类。例如,一个HTTP上下文,如果当前处理的是一个POST请求,在`CatsController`中绑定`create()`方法。`getHandler()`返回`create()`方法和`getClass()`方法所在的`CatsController`类的引用(不是实例)。
```typescript
const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"
```
访问对当前类和处理程序方法的引用的能力提供了极大的灵活性。最重要的是,它让我们有机会通过`@SetMetadata()`装饰器从守卫或拦截器中访问元数据集。我们将在下面介绍这个用例。
### 反射和元数据
Nest提供了通过`@SetMetadata()`装饰器将自定义元数据附加在路径处理程序的能力。我们可以在类中获取这些元数据来执行特定决策。
>cats.controller.ts
```typescript
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
```
> `@SetMetadata()`装饰器从`@nestjs/common`导入。
基于上述结构,我们将`roles`元数据(`roles`是一个元数据,并且`['admin']` 是对应的值)关联到`create()`方法。在这种情况下,不推荐直接在路径中使用`@SetMetadata()`,而是应该如下创建自己的装饰器:
>roles.decorator.ts
```typescript
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
```
这种方法更干净、更易读,并且是强类型的。我们现在可以使用自定义的`@Roles()`装饰器,并将其应用在`create()`方法中。
>cats.controller.ts
```typescript
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
```
要访问`roles`路径 (自定义元数据),我们将使用`Reflector`辅助类,它由框架提供,开箱即用,从`@nestjs/core`包导入。`Reflector`可以通过常规方式注入到类:
>roles.guard.ts
```typescript
@Injectable()
export class RolesGuard {
constructor(private reflector: Reflector) {}
}
```
> `Reflector`类从`@nestjs/core`导入。
使用`get()`方法读取处理程序的元数据。
```typescript
const roles = this.reflector.get<string[]>('roles', context.getHandler());
```
`Reflector#get`方法允许通过传递两个参数简单获取元数据:一个元数据key和一个context(装饰器对象)来获取元数据。在本例中,指定的key是`roles`(向上指回`roles.decorator.ts`以及在此处调用的`SetMetadata()`方法)。context 由`context.getHandler()`提供,用于从当前路径处理程序中获取元数据,`getHandler()`给了我们一个到路径处理函数的引用。
我们也可以组织我们的控制器,来从控制器层获取元数据,以在控制器所有路径中应用。
>cats.controller.ts
```typescript
@Roles('admin')
@Controller('cats')
export class CatsController {}
```
在本例中,要获取控制器元数据,将`context.getClass()`作为第二个参数(将控制器类作为上下文提供以获取元数据)来替代`context.getHandler()`:
>roles.guard.ts
```typescript
const roles = this.reflector.get<string[]>('roles', context.getClass());
```
要具备在多层提供元数据的能力,需要从多个上下文获取与合并元数据。`Reflector`类提供两个应用方法来帮助实现该功能。这些方法同时获取控制器和方法元数据,并通过不同方法来合并他们。
考虑以下场景,在两个水平应用`roles`都提供了元数据:
>cats.controller.ts
```typescript
@Roles('user')
@Controller('cats')
export class CatsController {
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
}
```
如果你想将`user`指定为默认角色,并且出于特定目的有选择地进行覆盖,可以使用
`getAllAndOverride()`方法。
```typescript
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
```
使用该代码编写守卫,在上下文中应用`create()`方法,采用上述元数据,将生成包含 `['admin']`的`roles`。
要获取与合并元数据(该方法合并数组和对象),使用`getAllAndMerge()`方法:
```typescript
const roles = this.reflector.getAllAndMerge<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
```
这会生成包含['user', 'admin']的`roles`。
对于这两种合并方法,传输元数据作为第一个参数,数组或者元数据对象上下文(例如,调用`getHandler()`和/或`getClass()`)作为第二个参数。
- 介绍
- 概述
- 第一步
- 控制器
- 提供者
- 模块
- 中间件
- 异常过滤器
- 管道
- 守卫
- 拦截器
- 自定义装饰器
- 基础知识
- 自定义提供者
- 异步提供者
- 动态模块
- 注入作用域
- 循环依赖
- 模块参考
- 懒加载模块
- 应用上下文
- 生命周期事件
- 跨平台
- 测试
- 技术
- 数据库
- 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?