网关
本文档其他地方讨论的大多数概念,如依赖注入、装饰器、异常过滤器、管道、守卫和拦截器,同样适用于网关。在可能的情况下,Nest 会抽象实现细节,以便相同的组件可以跨 HTTP 平台、WebSocket 和微服务运行。本节介绍 Nest 中特定于 WebSocket 的方面。
在 Nest 中,网关只是一个用 @WebSocketGateway() 装饰器注解的类。从技术上讲,网关是平台无关的,一旦创建了适配器,它们就与任何 WebSocket 库兼容。开箱即用支持两个 WS 平台:socket.io 和 ws。你可以选择最适合自己需求的一个。此外,你可以按照这个指南构建自己的适配器。

提示
网关可以被视为提供者;这意味着它们可以通过类构造函数注入依赖。同样,网关也可以被其他类(提供者和控制器)注入。
安装
要开始构建基于 WebSocket 的应用程序,首先安装所需的包:
$ npm i --save @nestjs/websockets @nestjs/platform-socket.io概述
通常,每个网关都监听与 HTTP 服务器相同的端口,除非你的应用程序不是 Web 应用程序,或者你手动更改了端口。可以通过向 @WebSocketGateway(80) 装饰器传递参数来修改此默认行为,其中 80 是选择的端口号。你还可以使用以下方式设置网关使用的命名空间:
@WebSocketGateway(80, { namespace: 'events' })警告
网关在被引用到现有模块的 providers 数组之前不会被实例化。
你可以将任何支持的选项作为第二个参数传递给 @WebSocketGateway() 装饰器的 socket 构造函数,如下所示:
@WebSocketGateway(81, { transports: ['websocket'] })网关现在正在监听,但我们尚未订阅任何传入消息。让我们创建一个处理器来订阅 events 消息,并用完全相同的数据响应用户。
// events.gateway
@SubscribeMessage('events')
handleEvent(@MessageBody() data: string): string {
return data;
}提示
@SubscribeMessage() 和 @MessageBody() 装饰器从 @nestjs/websockets 包中导入。
创建网关后,我们可以在模块中注册它。
import { Module } from '@nestjs/common';
import { EventsGateway } from './events.gateway';
// events.module
@Module({
providers: [EventsGateway]
})
export class EventsModule {}你还可以向装饰器传递属性键,以从传入消息体中提取它:
// events.gateway
@SubscribeMessage('events')
handleEvent(@MessageBody('id') id: number): number {
// id === messageBody.id
return id;
}如果你不想使用装饰器,以下代码在功能上是等效的:
// events.gateway
@SubscribeMessage('events')
handleEvent(client: Socket, data: string): string {
return data;
}在上面的示例中,handleEvent() 函数接受两个参数。第一个是平台特定的 socket 实例,第二个是从客户端接收到的数据。但是不建议使用这种方式,因为它需要在每个单元测试中模拟 socket 实例。
一旦收到 events 消息,处理器会发送一个确认,其中包含通过网络发送的相同数据。此外,还可以使用特定于库的方式发出消息,例如利用 client.emit() 方法。要访问已连接的 socket 实例,请使用 @ConnectedSocket() 装饰器。
// events.gateway
@SubscribeMessage('events')
handleEvent(
@MessageBody() data: string,
@ConnectedSocket() client: Socket,
): string {
return data;
}提示
@ConnectedSocket() 装饰器从 @nestjs/websockets 包中导入。
但是,在这种情况下,你将无法利用拦截器。如果你不想响应用户,可以简单地跳过 return 语句(或显式返回一个"假值",例如 undefined)。
现在,当客户端按以下方式发出消息时:
socket.emit('events', { name: 'Nest' });handleEvent() 方法将被执行。要监听从上述处理器中发出的消息,客户端必须附加一个相应的确认监听器:
socket.emit('events', { name: 'Nest' }, (data) => console.log(data));虽然从消息处理器返回值会隐式发送确认,但高级场景通常需要对确认回调进行直接控制。
@Ack() 参数装饰器允许将 ack 回调函数直接注入消息处理器。如果不使用该装饰器,此回调将作为方法的第三个参数传递。
// events.gateway
@SubscribeMessage('events')
handleEvent(
@MessageBody() data: string,
@Ack() ack: (response: { status: string; data: string }) => void,
) {
ack({ status: 'received', data });
}多响应
确认只会分派一次。此外,原生 WebSocket 实现不支持确认。要解决此限制,你可以返回一个由两个属性组成的对象:event 是发出事件的名称,data 是必须转发给客户端的数据。
// events.gateway
@SubscribeMessage('events')
handleEvent(@MessageBody() data: unknown): WsResponse<unknown> {
const event = 'events';
return { event, data };
}提示
WsResponse 接口从 @nestjs/websockets 包中导入。
警告
如果你的 data 字段依赖于 ClassSerializerInterceptor,则应该返回一个实现 WsResponse 的类实例,因为它会忽略普通 JavaScript 对象响应。
要监听传入的响应,客户端必须应用另一个事件监听器。
socket.on('events', (data) => console.log(data));异步响应
消息处理器可以同步或异步响应。因此,支持 async 方法。消息处理器还可以返回一个 Observable,在这种情况下,结果值将被发出,直到流完成。
// events.gateway
@SubscribeMessage('events')
onEvent(@MessageBody() data: unknown): Observable<WsResponse<number>> {
const event = 'events';
const response = [1, 2, 3];
return from(response).pipe(
map(data => ({ event, data })),
);
}在上面的示例中,消息处理器将响应 3 次(数组中的每个元素各一次)。
生命周期钩子
有 3 个有用的生命周期钩子可用。它们都有对应的接口,如下表所述:
OnGatewayInit | 强制实现 afterInit() 方法。接收特定于库的服务器实例作为参数(如果需要,还可以展开其余参数)。 |
OnGatewayConnection | 强制实现 handleConnection() 方法。接收特定于库的客户端 socket 实例作为参数。 |
OnGatewayDisconnect | 强制实现 handleDisconnect() 方法。接收特定于库的客户端 socket 实例作为参数。 |
提示
每个生命周期接口都从 @nestjs/websockets 包中导出。
服务器与命名空间
有时,你可能想直接访问原生的、平台特定的服务器实例。该对象的引用作为参数传递给 afterInit() 方法(OnGatewayInit 接口)。另一种选择是使用 @WebSocketServer() 装饰器。
@WebSocketServer()
server: Server;此外,你可以使用 namespace 属性检索相应的命名空间,如下所示:
@WebSocketGateway({ namespace: 'my-namespace' })
export class EventsGateway {
@WebSocketServer()
namespace: Namespace;
}@WebSocketServer() 装饰器通过引用 @WebSocketGateway() 装饰器存储的元数据来注入服务器实例。如果你为 @WebSocketGateway() 装饰器提供了 namespace 选项,@WebSocketServer() 装饰器将返回 Namespace 实例而不是 Server 实例。
注意
@WebSocketServer() 装饰器从 @nestjs/websockets 包中导入。
Nest 会在服务器实例准备就绪后自动将其分配给此属性。
示例
一个可运行的示例可在这里获取。