🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Websocket ## 网关 本文档中其他地方讨论的大多数概念,如依赖注入、装饰器、异常过滤器、管道、守卫和拦截器,都同样适用于网关。只要有可能,Nest将抽象实现细节,以便相同的组件可以跨基于 `http` 的平台、`WebSockets` 和微服务运行。本节将介绍 `WebSockets` 在 `Nest` 中的应用。 在 `Nest` 中,网关只是一个用 `@WebSocketGateway()` 装饰器注解的类。从技术上讲,网关与平台无关,这使得它们在创建适配器之后就可以与任何 `WebSockets` 库兼容。有两个开箱即用的WS平台:[socket.io](https://github.com/socketio/socket.io)和[ws](https://github.com/websockets/ws)。你可以选择最适合你需要的。另外,您可以按照本指南构建自己的适配器。 ![](https://docs.nestjs.com/assets/Gateways_1.png) > 网关可以被看作是`provider`,这意味着它可以毫不费力地通过构造函数注入依赖关系。另外,网关也可以由其他类(提供者和控制器)注入。 ### 安装 要开始构建基于WebSockets的应用,首先,我们需要安装所需的软件包: ```bash $ npm i --save @nestjs/websockets @nestjs/platform-socket.io ``` ### 概述 一般来说,除非你的应用程序不是 `Web` 应用程序,或者您已手动更改端口,否则每个网关都会在**HTTP服务器**运行时监听相同的端口。我们可以通过将参数传递给 `@WebSocketGateway(80)` 装饰器来改变这种行为,其中 `80` 是一个选定的端口号。另外,您可以使用以下构造来设置此网关使用的[命名空间](https://socket.io/docs/rooms-and-namespaces/): ```typescript @WebSocketGateway(80, { namespace: 'events' }) ``` > 只有将网关放入当前模块的 `providers` 数组中,网关才会实例化。 你可以在 `@WebSocketGateway()` 装饰器的第二个参数中给socket构造函数传入任何支持的选项,如下所示: ```typescript @WebSocketGateway(81, { transports: ['websocket'] }) ``` 现在,网关现在正在监听,但我们目前尚未订阅收到的消息。让我们创建一个处理程序,它将订阅`events`消息并使用完全相同的数据响应用户。 >events.gateway.ts ```typescript @SubscribeMessage('events') handleEvent(@MessageBody() data: string): string { return data; } ``` > `@SubscribeMessage()` 和 `@MessageBody()` 装饰器是从 `@nestjs/websockets` 包中导入的。 如果你不想使用装饰器,下面的代码在功能上是等价的: > events.gateway.ts ```typescript @SubscribeMessage('events') handleEvent(client: Socket, data: string): string { return data; } ``` 该 `handleEvent()` 函数有两个参数。第一个是特定于平台的[socket](https://socket.io/docs/server-api/#socket)实例,第二个是从客户端接收的数据。但是不建议使用此方法,因为它需要在每个单元测试中模拟 `socket` 实例。 收到消息后,我们会发送一个确认信息,其中包含某人通过网络发送的相同数据。此外,可以使用特定于库的方法发出消息,例如,通过使用 `client.emit()` 方法。 为了访问连接的 `socket` 实例,请使用 `@ConnectedSocket()` 装饰器。 > events.gateway.ts ```typescript @SubscribeMessage('events') handleEvent( @MessageBody() data: string, @ConnectedSocket() client: Socket, ): string { return data; } ``` > `@ConnectedSocket()` 装饰器是从 `@nestjs/websockets` 包中导入的。 但是,在这种情况下,您将无法利用拦截器。如果你不想响应用户,你可以简单地跳过 `return` 语句(或者显式地返回 'falsy' 值,例如 'undefined' )。 现在,当客户端发出的消息如下: ```typescript socket.emit('events', { name: 'Nest' }); ``` 将执行 `handleEvent()` `法。此外,为了侦听从上述处理程序中发出的消息,客户端必须附加相应的侦听器: ```typescript socket.emit('events', { name: 'Nest' }, data => console.log(data)); ``` ### 多个响应 确认仅发送一次。而且,原生 `WebSockets` 不支持它。要解决这个限制,可以返回一个包含两个属性的对象。发射事件的名称 `event` 和将要转发给客户端的 `data` 。 > events.gateway.ts ```typescript @SubscribeMessage('events') handleEvent(@MessageBody() data: unknown): WsResponse<unknown> { const event = 'events'; return { event, data }; } ``` > `WsResponse` 接口是从 `@nestjs/websockets` 包中导入的。 > **警告** 如果您的数据字段依赖于 `ClassSerializerInterceptor`,您应该返回一个实现 `WsResponse` 的类实例,因为它会忽略纯 JavaScript 对象响应。 为了侦听传入的响应,客户端必须应用另一个事件侦听器。 ```typescript socket.on('events', data => console.log(data)); ``` ### 异步响应 消息处理程序可以同步或异步响应。因此,也支持异步方法。消息处理程序还能够返回一个 `Observable` 对象,在这种情况下,结果值将被出去,直到流完成。 > events.gateway.ts ```typescript @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()` 装饰器。 偶尔,您可能希望直接访问原生`特定库`的服务器实例。此对象的引用作为参数传递给`afterInit()`方法(`OnGatewayInit`接口)。另一个选项是使用 `@WebSocketServer()` 装饰器。 ```typescript @WebSocketServer() server: Server; ``` > `@WebSocketServer()` 装饰器是从 `@nestjs/websockets` 包中导入的。 当它准备好使用时,`Nest` 会自动将服务器实例分配给该属性。 [这里](https://github.com/nestjs/nest/tree/master/sample/02-gateways)有一个可用的例子