使用NestJs提供WebSocket服務。
本文會在新建專案的基礎上增加2個類
- Gateway 實現業務邏輯的地方
- WebSocketAdapter WebSocket介面卡
新建專案
新建一個專案來演示,用npm來管理專案。
nest new websocket-start
得到一個有基礎功能的工程。
進入專案目錄,安裝2個庫
npm i --save @nestjs/websockets @nestjs/platform-socket.io
啟動
使用埠3001
await app.listen(3001);
npm run start
啟動我們的工程。用postman測一下,功能ok。
gateway介紹
Nest裡的gateway(閘道器)只是一個用 @WebSocketGateway()
裝飾器註釋的類。從技術上講,閘道器與平臺無關,在建立介面卡後它們與任何 WebSockets 庫都相容。
新建Gateway
新建ws.gateway.ts檔案。在裝飾器@WebSocketGateway()
裡埠指定為3002。
import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway } from "@nestjs/websockets";
import * as WebSocket from 'ws';
@WebSocketGateway(3002)
export class WsStartGateway {
@SubscribeMessage('hello')
hello(@MessageBody() data: any): any {
return {
"event": "hello",
"data": data,
"msg": 'rustfisher.com'
};
}
}
裡面有一個hello
方法,訂閱的訊息是'hello'
。
把它放進AppModule
的providers
裡。
providers: [WsStartGateway],
如果websockt和http用了同一個介面(本例是3001),啟動時會報錯
Error: listen EADDRINUSE: address already in use :::3001
因此我們這裡給ws分配另一個埠號。
獲取WebSocket物件
在WsStartGateway
裡新增加一個訊息訂閱方法。
方法裡接受@ConnectedSocket() client: WebSocket
,這個client就是與客戶端的連線物件。
我們可以用它來給客戶端傳送訊息。
@SubscribeMessage('hello2')
hello2(@MessageBody() data: any, @ConnectedSocket() client: WebSocket): any {
console.log('收到訊息 client:', client);
client.send(JSON.stringify({ event: 'tmp', data: '這裡是個臨時資訊' }));
return { event: 'hello2', data: data };
}
自定義WebSocketAdapter
前面我們建立好了Gateway,還需要一個介面卡。
新建檔案ws.adapter.ts,繼承WebSocketAdapter
import * as WebSocket from 'ws';
import { WebSocketAdapter, INestApplicationContext } from '@nestjs/common';
import { MessageMappingProperties } from '@nestjs/websockets';
import { Observable, fromEvent, EMPTY } from 'rxjs';
import { mergeMap, filter } from 'rxjs/operators';
export class WsAdapter implements WebSocketAdapter {
constructor(private app: INestApplicationContext) { }
create(port: number, options: any = {}): any {
console.log('ws create')
return new WebSocket.Server({ port, ...options });
}
bindClientConnect(server, callback: Function) {
console.log('ws bindClientConnect, server:\n', server);
server.on('connection', callback);
}
bindMessageHandlers(
client: WebSocket,
handlers: MessageMappingProperties[],
process: (data: any) => Observable<any>,
) {
console.log('[waAdapter]有新的連線進來')
fromEvent(client, 'message')
.pipe(
mergeMap(data => this.bindMessageHandler(client, data, handlers, process)),
filter(result => result),
)
.subscribe(response => client.send(JSON.stringify(response)));
}
bindMessageHandler(
client: WebSocket,
buffer,
handlers: MessageMappingProperties[],
process: (data: any) => Observable<any>,
): Observable<any> {
let message = null;
try {
message = JSON.parse(buffer.data);
} catch (error) {
console.log('ws解析json出錯', error);
return EMPTY;
}
const messageHandler = handlers.find(
handler => handler.message === message.event,
);
if (!messageHandler) {
return EMPTY;
}
return process(messageHandler.callback(message.data));
}
close(server) {
console.log('ws server close');
server.close();
}
}
在bindMessageHandler
方法中,會將傳來的json訊息解析,然後傳送到對應的處理器中。
這裡就是發給gateway進行處理。
判斷依據是message.event
,就是event
欄位。
在main.ts裡使用這個介面卡。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WsAdapter } from './ws/ws.adapter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app)); // 使用我們的介面卡
await app.listen(3001);
}
bootstrap();
npm run start
執行專案,準備進一步測試。
用Postman來測試WebSocket
Postman8.8.0提供了beta版的WebSocket測試功能。
New -> WebSocket Request beta
新建一個WebSocket測試。當前版本還不支援儲存ws的測試例子。
輸入目標url ws://localhost:3002
,點選連線 Connect 按鈕。
傳送測試訊息。在訊息框裡填入以下json資料。
{
"event" : "hello",
"data" : "測試資料"
}
傳送的資料經過WsAdapter
分發給WsStartGateway
,處理後返回資料。
傳送hello2測試資料
{
"event" : "hello2",
"data" : "測試資料"
}
可以看到服務返回了2條資料。
傳送一個錯誤格式的資料
{
"event" : "hello2
服務端接收到了資料,但是解析失敗
ws解析json出錯 SyntaxError: Unexpected end of JSON input
小結
要使用WebSocket功能,需要增加
- Gateway 實現業務邏輯的地方
- WebSocketAdapter WebSocket介面卡
ws的埠建議是和http的埠分開。
參考
- NestJS WebSockets https://docs.nestjs.com/websockets/gateways
- NestJS 合集 https://rustfisher.com/categories/NestJS/
- 示例工程 https://gitee.com/rustfisher/nest-sample