网关 Gateways

导读

本文档其他地方讨论的大多数概念(例如依赖项注入、装饰器、异常过滤器、管道、保护和拦截器)同样适用于网关。只要有可能,Nest 就会抽象实现细节,以便相同的组件可以跨基于 HTTP 的平台、WebSocket 和微服务运行。本节介绍 Nest 中特定于 WebSocket 的方面。

在 Nest 中,网关只是一个用 @WebSocketGateway() 装饰器注释的类。从技术上讲,网关与平台无关,这使得它们在创建适配器后与任何 WebSockets 库兼容。有两个开箱即用的 WS 平台支持:socket.iows。您可以选择最适合您需求的平台。此外,您可以按照此 指南 构建自己的适配器。

img

提示

网关可以视为提供程序;这意味着它们可以通过类构造函数注入依赖项。此外,网关也可以由其他类(提供程序和控制器)注入。

安装

要开始构建基于 WebSockets 的应用程序,请首先安装所需的包:

bash
$ npm i --save @nestjs/websockets @nestjs/platform-socket.io
$ yarn i @nestjs/websockets @nestjs/platform-socket.io

概述

一般来说,每个网关都监听与 HTTP 服务器 相同的端口,除非您的应用不是 Web 应用,或者您手动更改了端口。可以通过将参数传递给 @WebSocketGateway(80) 装饰器来修改此默认行为,其中 80 是选定的端口号。您还可以使用以下构造设置网关使用的 命名空间

ts
@WebSocketGateway(80, { namespace: 'events' })
警告

网关只有在现有模块的提供程序数组中引用时才会实例化。

您可以将任何受支持的 选项 传递给套接字构造函数,并将第二个参数传递给 @WebSocketGateway() 装饰器,如下所示:

ts
@WebSocketGateway(81, { transports: ['websocket'] })

网关现在正在监听,但我们尚未订阅任何传入消息。让我们创建一个处理程序,它将订阅 events 消息并使用完全相同的数据响应用户。

ts
events.gateway
ts
@SubscribeMessage('events')
handleEvent(@MessageBody() data: string): string {
  return data;
}
提示

@SubscribeMessage()@MessageBody() 装饰器从 @nestjs/websockets 包导入。

创建网关后,我们可以将其注册到我们的模块中。

events.module
ts
import { Module } from '@nestjs/common'
import { EventsGateway } from './events.gateway'

@Module({
  providers: [EventsGateway]
})
export class EventsModule {}

您还可以将属性键传递给装饰器,以从传入的消息正文中提取它:

ts
events.gateway
ts
@SubscribeMessage('events')
handleEvent(@MessageBody('id') id: number): number {
  // id === messageBody.id
  return id;
}

如果您不想使用装饰器,则以下代码在功能上是等效的:

ts
events.gateway
ts
@SubscribeMessage('events')
handleEvent(client: Socket, data: string): string {
  return data;
}

在上面的例子中,handleEvent() 函数接受两个参数。第一个是特定于平台的 套接字实例,而第二个是从客户端接收的数据。但是不建议使用这种方法,因为它需要在每个单元测试中模拟 socket 实例。

一旦收到 events 消息,处理程序就会发送一个确认,其中包含通过网络发送的相同数据。此外,可以使用特定于库的方法发出消息,例如,通过使用 client.emit() 方法。为了访问已连接的套接字实例,请使用 @ConnectedSocket() 装饰器。

ts
events.gateway
ts
@SubscribeMessage('events')
handleEvent(
  @MessageBody() data: string,
  @ConnectedSocket() client: Socket,
): string {
  return data;
}
提示

@ConnectedSocket() 装饰器从 @nestjs/websockets 包导入。

但是,在这种情况下,您将无法利用拦截器。如果您不想响应用户,您可以简单地跳过 return 语句(或明确返回值,例如 undefined)。

现在,当客户端发出以下消息时:

ts
socket.emit('events', { name: 'Nest' })

handleEvent() 方法将被执行。为了监听从上述处理程序中发出的消息,客户端必须附加相应的确认监听器:

ts
socket.emit('events', { name: 'Nest' }, data => console.log(data))

多个响应

确认仅发送一次。此外,本机 WebSockets 实现不支持它。为了解决此限制,您可以返回一个由两个属性组成的对象。event 是发出的事件的名称,而 data 必须转发给客户端。

ts
events.gateway
ts
@SubscribeMessage('events')
handleEvent(@MessageBody() data: unknown): WsResponse<unknown> {
  const event = 'events';
  return { event, data };
}
提示

WsResponse 接口从 @nestjs/websockets 包导入。

警告

如果您的 data 字段依赖于 ClassSerializerInterceptor,则应返回实现 WsResponse 的类实例,因为它会忽略普通的 JavaScript 对象响应。

为了监听传入的响应,客户端必须应用另一个事件监听器。

ts
socket.on('events', data => console.log(data))

异步响应

消息处理程序能够同步或异步响应。因此,支持async方法。消息处理程序还能够返回Observable,在这种情况下,结果值将被发出,直到流完成。

ts
events.gateway
ts
@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 个有用的生命周期钩子可用。它们都有相应的接口,并在下表中描述:

OnGatewayInitForces to implement the afterInit() method. Takes library-specific server instance as an argument (and spreads the rest if required).
OnGatewayConnectionForces to implement the handleConnection() method. Takes library-specific client socket instance as an argument.
OnGatewayDisconnectForces to implement the handleDisconnect() method. Takes library-specific client socket instance as an argument.
提示

每个生命周期接口都从 @nestjs/websockets 包中公开。

服务器和命名空间

有时,您可能希望直接访问本机的特定于平台的服务器实例。对此对象的引用作为参数传递给 afterInit() 方法(OnGatewayInit 接口)。另一个选项是使用 @WebSocketServer() 装饰器。

ts
@WebSocketServer()
server: Server;

此外,您可以使用 namespace 属性检索相应的命名空间,如下所示:

ts
@WebSocketServer({ namespace: 'my-namespace' })
namespace: Namespace;
通知

@WebSocketServer() 装饰器从 @nestjs/websockets 包导入。

一旦服务器实例可供使用,Nest 将自动将其分配给此属性。

示例

此处 提供了一个工作示例。