网关

网关是用@WebSocketGateway()装饰器注解的类。默认情况下,网关使用 socket.io包,但也提供了与广泛的其他库的兼容性,包括本地web套接字实现(阅读更多)。

WEBSOCKETS - 图1

?> 提示网关的行为与简单的提供者相同,因此它可以毫不费力地通过构造函数注入依赖关系。另外,网关也可以由其他类(提供者和控制器)注入。

安装

首先,我们需要安装所需的软件包:

  1. $ npm i --save @nestjs/websockets

基本

一般来说,除非你的应用程序不是Web应用程序,或者您已手动更改端口,否则每个网关都会在HTTP服务器运行时监听相同的端口。我们可以通过将参数传递给@WebSocketGateway(81)装饰器来改变这种行为,其中81是一个选定的端口号。另外,您可以使用以下构造来设置此网关使用的命名空间

  1. @WebSocketGateway(81, { namespace: 'events' })

!> 警告只有将网关放入提供程序数组中,网关才会启动。

命名空间不是唯一可用的选项。您可以传递此处提及的任何其他财产。这些属性将在实例化过程中传递给套接字构造函数。好的,网关现在正在监听,但我们目前尚未订阅收到的消息。让我们创建一个处理程序,它将订阅事件消息并使用完全相同的数据响应用户。

events.gateway.ts

  1. @SubscribeMessage('events')
  2. onEvent(client, data: any): WsResponse<any> {
  3. const event = 'events';
  4. return { event, data };
  5. }

?> 提示WsResponse接口和@SubscribeMessage()装饰器都从@ nestjs / common包中导入。

onEvent()函数有2个参数。第一个是库特定的套接字实例,第二个是从客户端接收的数据。从函数返回的对象必须有2个属性。事件是发出事件的名称以及必须转发给客户端的数据。此外,可以使用特定于库的方法发送消息,例如,通过使用client.emit()方法。但是,在这种情况下,您无法使用拦截器。如果你不想回应用户,只是不要返回任何东西(或明确返回“falsy”值,例如,未定义)。

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

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

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

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

异步响应

每个消息处理程序可以是同步的或异步的(异步),因此您可以返回Promise。此外,你可以返回RxJS Observable,这意味着你可以返回多个值(它们将被发射,直到流完成)。

events.gateway.ts

  1. @SubscribeMessage('events')
  2. onEvent(client, data: any: Observable<WsResponse<number>> {
  3. const event = 'events';
  4. const response = [1, 2, 3];
  5. return from(response).pipe(
  6. map(data => ({ event, data })),
  7. );
  8. }

上面的消息处理程序将响应3次(从响应数组中的每个项目按顺序)。

生命周期挂钩

有3个有用的生命周期挂钩。它们都有相应的接口,并在下表中进行描述:

OnGatewayInit 强制执行afterInit()方法。将特定于库的服务器实例作为参数
OnGatewayConnection 强制执行handleConnection()方法。将特定于库的客户端套接字实例作为参数。
OnGatewayDisconnect 强制执行handleDisconnect()方法。将特定于库的客户端套接字实例作为参数。

?>提示每个生命周期接口都来自@ nestjs / websockets包。

特定库的服务器实例

偶尔,您可能希望直接访问本地特定库的服务器实例。此对象的引用作为参数传递给afterInit()方法(OnGatewayInit接口)。第二种方法是使用@WebSocketServer()装饰器。

  1. @WebSocketServer() server;

?>注意@WebSocketServer()装饰器是从@ nestjs / websockets包中导入的。

当它准备好使用时,Nest会自动将服务器实例分配给该属性。

这里有一个可用的例子

异常过滤器

websockets的异常处理层工作原理与prime层完全相同。唯一的区别是不要抛出HttpException,你应该抛出WsException

  1. throw new WsException('Invalid credentials.');

!>注意WsException类是从@ nestjs / websockets包中导入的。

Nest会处理这个异常并用下列数据发出异常消息:

  1. {
  2. status: 'error',
  3. message: 'Invalid credentials.'
  4. }

异常过滤器

异常过滤器也是非常类似的,并且工作方式与主过滤器完全相同。

ws-exception.filter.ts

  1. import { Catch, WsExceptionFilter } from '@nestjs/common';
  2. import { WsException } from '@nestjs/websockets';
  3. @Catch(WsException)
  4. export class ExceptionFilter implements WsExceptionFilter {
  5. catch(exception: WsException, client) {
  6. client.emit('exception', {
  7. status: 'error',
  8. message: `It's a message from the exception filter`,
  9. });
  10. }
  11. }

!>注意全局设置websockets异常过滤器是不可能的。

管道

websockets管道普通管道没有区别。唯一应该注意的是,不要抛出HttpException,而应该使用WsException

!>提示WsException类在@socketjs / websockets包中可用。

看守器

常规看守器和websockets看守器之间有一个区别。websockets guard将从客户端传递的数据而不是expressjs请求对象作为canActivate()函数的参数。此外,当警卫返回false时,它会抛出WsException(而不是HttpException)。

!>提示WsException类在@socketjs / websockets包中可用。

拦截器

常规拦截器和websockets拦截器之间有一个区别。 Websockets拦截器将从客户端传递的数据而不是expressjs请求对象作为intercept()函数的参数。

适配器

Nest websockets模块基于socket.io,但您可以使用WebSocketAdapter接口来引入自己的库。该界面强制实施下表中描述的几种方法:

create 将套接字实例连接到指定的端口
bindClientConnect 绑定客户端连接
bindMessageHandlers 将传入的消息绑定到适当的消息处理程序

另外,还有两种可选的方法:

createWithNamespace 将套接字实例附加到指定的端口和名称空间(如果您的库支持空间)
bindClientDisconnect 绑定客户端断开连接事件

出于演示目的,我们将把ws库与Nest应用程序集成在一起。

ws-adapter.ts

  1. import * as WebSocket from 'ws';
  2. import { WebSocketAdapter } from '@nestjs/common';
  3. import { MessageMappingProperties } from '@nestjs/websockets';
  4. import { Observable } from 'rxjs/Observable';
  5. import 'rxjs/add/observable/fromEvent';
  6. import 'rxjs/add/observable/empty';
  7. import 'rxjs/add/operator/switchMap';
  8. import 'rxjs/add/operator/filter';
  9. export class WsAdapter implements WebSocketAdapter {
  10. create(port: number) {
  11. return new WebSocket.Server({ port });
  12. }
  13. bindClientConnect(server, callback: (...args: any[]) => void) {
  14. server.on('connection', callback);
  15. }
  16. bindMessageHandlers(client: WebSocket, handlers: MessageMappingProperties[], process: (data) => Observable<any>) {
  17. Observable.fromEvent(client, 'message')
  18. .switchMap((buffer) => this.bindMessageHandler(buffer, handlers, process))
  19. .filter((result) => !!result)
  20. .subscribe((response) => client.send(JSON.stringify(response)));
  21. }
  22. bindMessageHandler(buffer, handlers: MessageMappingProperties[], process: (data) => Observable<any>): Observable<any> {
  23. const data = JSON.parse(buffer.data);
  24. const messageHandler = handlers.find((handler) => handler.message === data.type);
  25. if (!messageHandler) {
  26. return Observable.empty();
  27. }
  28. const { callback } = messageHandler;
  29. return process(callback(data));
  30. }
  31. }

由于WsAdapter类可以使用,我们可以使用useWebSocketAdapter()方法设置适配器:

main.ts

  1. const app = await NestFactory.create(ApplicationModule);
  2. app.useWebSocketAdapter(new WsAdapter());

现在Nest会使用我们的WsAdapter而不是默认的WsAdapter。