使用NestJs提供WebSocket服務。

本文會在新建專案的基礎上增加2個類

  • Gateway 實現業務邏輯的地方
  • WebSocketAdapter WebSocket介面卡

新建專案

新建一個專案來演示,用npm來管理專案。

  1. nest new websocket-start

得到一個有基礎功能的工程。

進入專案目錄,安裝2個庫

  1. npm i --save @nestjs/websockets @nestjs/platform-socket.io

啟動

使用埠3001

  1. await app.listen(3001);

npm run start啟動我們的工程。用postman測一下,功能ok。

gateway介紹

Nest裡的gateway(閘道器)只是一個用 @WebSocketGateway() 裝飾器註釋的類。從技術上講,閘道器與平臺無關,在建立介面卡後它們與任何 WebSockets 庫都相容。

新建Gateway

新建ws.gateway.ts檔案。在裝飾器@WebSocketGateway()裡埠指定為3002。

  1. import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway } from "@nestjs/websockets";
  2. import * as WebSocket from 'ws';
  3. @WebSocketGateway(3002)
  4. export class WsStartGateway {
  5. @SubscribeMessage('hello')
  6. hello(@MessageBody() data: any): any {
  7. return {
  8. "event": "hello",
  9. "data": data,
  10. "msg": 'rustfisher.com'
  11. };
  12. }
  13. }

裡面有一個hello方法,訂閱的訊息是'hello'

把它放進AppModuleproviders裡。

  1. providers: [WsStartGateway],

如果websockt和http用了同一個介面(本例是3001),啟動時會報錯

  1. Error: listen EADDRINUSE: address already in use :::3001

因此我們這裡給ws分配另一個埠號。

獲取WebSocket物件

WsStartGateway裡新增加一個訊息訂閱方法。

方法裡接受@ConnectedSocket() client: WebSocket,這個client就是與客戶端的連線物件。

我們可以用它來給客戶端傳送訊息。

  1. @SubscribeMessage('hello2')
  2. hello2(@MessageBody() data: any, @ConnectedSocket() client: WebSocket): any {
  3. console.log('收到訊息 client:', client);
  4. client.send(JSON.stringify({ event: 'tmp', data: '這裡是個臨時資訊' }));
  5. return { event: 'hello2', data: data };
  6. }

自定義WebSocketAdapter

前面我們建立好了Gateway,還需要一個介面卡。

新建檔案ws.adapter.ts,繼承WebSocketAdapter

  1. import * as WebSocket from 'ws';
  2. import { WebSocketAdapter, INestApplicationContext } from '@nestjs/common';
  3. import { MessageMappingProperties } from '@nestjs/websockets';
  4. import { Observable, fromEvent, EMPTY } from 'rxjs';
  5. import { mergeMap, filter } from 'rxjs/operators';
  6. export class WsAdapter implements WebSocketAdapter {
  7. constructor(private app: INestApplicationContext) { }
  8. create(port: number, options: any = {}): any {
  9. console.log('ws create')
  10. return new WebSocket.Server({ port, ...options });
  11. }
  12. bindClientConnect(server, callback: Function) {
  13. console.log('ws bindClientConnect, server:\n', server);
  14. server.on('connection', callback);
  15. }
  16. bindMessageHandlers(
  17. client: WebSocket,
  18. handlers: MessageMappingProperties[],
  19. process: (data: any) => Observable<any>,
  20. ) {
  21. console.log('[waAdapter]有新的連線進來')
  22. fromEvent(client, 'message')
  23. .pipe(
  24. mergeMap(data => this.bindMessageHandler(client, data, handlers, process)),
  25. filter(result => result),
  26. )
  27. .subscribe(response => client.send(JSON.stringify(response)));
  28. }
  29. bindMessageHandler(
  30. client: WebSocket,
  31. buffer,
  32. handlers: MessageMappingProperties[],
  33. process: (data: any) => Observable<any>,
  34. ): Observable<any> {
  35. let message = null;
  36. try {
  37. message = JSON.parse(buffer.data);
  38. } catch (error) {
  39. console.log('ws解析json出錯', error);
  40. return EMPTY;
  41. }
  42. const messageHandler = handlers.find(
  43. handler => handler.message === message.event,
  44. );
  45. if (!messageHandler) {
  46. return EMPTY;
  47. }
  48. return process(messageHandler.callback(message.data));
  49. }
  50. close(server) {
  51. console.log('ws server close');
  52. server.close();
  53. }
  54. }

bindMessageHandler方法中,會將傳來的json訊息解析,然後傳送到對應的處理器中。

這裡就是發給gateway進行處理。

判斷依據是message.event,就是event欄位。

main.ts裡使用這個介面卡。

  1. import { NestFactory } from '@nestjs/core';
  2. import { AppModule } from './app.module';
  3. import { WsAdapter } from './ws/ws.adapter';
  4. async function bootstrap() {
  5. const app = await NestFactory.create(AppModule);
  6. app.useWebSocketAdapter(new WsAdapter(app)); // 使用我們的介面卡
  7. await app.listen(3001);
  8. }
  9. bootstrap();

npm run start執行專案,準備進一步測試。

用Postman來測試WebSocket

Postman8.8.0提供了beta版的WebSocket測試功能。

New -> WebSocket Request beta新建一個WebSocket測試。當前版本還不支援儲存ws的測試例子。

輸入目標url ws://localhost:3002,點選連線 Connect 按鈕。

傳送測試訊息。在訊息框裡填入以下json資料。

  1. {
  2. "event" : "hello",
  3. "data" : "測試資料"
  4. }

傳送的資料經過WsAdapter分發給WsStartGateway,處理後返回資料。

傳送hello2測試資料

  1. {
  2. "event" : "hello2",
  3. "data" : "測試資料"
  4. }

可以看到服務返回了2條資料。

傳送一個錯誤格式的資料

  1. {
  2. "event" : "hello2

服務端接收到了資料,但是解析失敗

  1. ws解析json出錯 SyntaxError: Unexpected end of JSON input

小結

要使用WebSocket功能,需要增加

  • Gateway 實現業務邏輯的地方
  • WebSocketAdapter WebSocket介面卡

ws的埠建議是和http的埠分開。

參考