【nestjs】使用websocket协议通信

前言

nestjs特别青睐socket.io,文档中关于网关的描述都是以socket.io为例,并且不管是@nestjs/platform-socket.io还是@nestjs/platform-ws的适配器实现都是参考socket.io的,以至于想完整地使用websocket协议进行通信变得难以实现。

WsAdapter遇到的坑

想要使用ws库,需要使用@nestjs/platform-wsWsAdapter,使用方法如下:

// main.ts
// 必须在app.listen方法之前调用
 app.useWebSocketAdapter(new WsAdapter(app));
 await app.listen(3000);

然后开始踩坑。

@SubscribeMessage订阅事件装饰器

在websocket协议里,是没有订阅事件的概念的,这是socket.io里的概念。这里我的解决办法是写死事件名,例如@SubscribeMessage('message')

客户端调用send的方法,服务器无法收到

排除客户端出现问题的可能,测试了一些特定格式的JSON数据是可以的,例如:

{
	"event": "message",
	"data": {
		"test": 123
	}
}

翻了源码,发现是需要按照上述格式的JSON的数据解析的。

// ws-adapter.ts
 const message = JSON.parse(buffer.data);
 const messageHandler = handlers.find(
   handler => handler.message === message.event,
 );
 const { callback } = messageHandler;
 return transform(callback(message.data));

解决方案

@nestjs/platform-ws适配器对传输数据进行了封装,因此我们只要自己写一个适配器就可以解决这个问题。
源码如下:

// src/adapters/ws.adapter.ts
import { WsAdapter as OriginWsAdapter } from '@nestjs/platform-ws';
import { MessageMappingProperties } from '@nestjs/websockets';
import {
  EMPTY,
  filter,
  first,
  fromEvent,
  mergeMap,
  Observable,
  share,
  takeUntil,
} from 'rxjs';
import { CLOSE_EVENT } from '@nestjs/websockets/constants';

enum READY_STATE {
  CONNECTING_STATE = 0,
  OPEN_STATE = 1,
  CLOSING_STATE = 2,
  CLOSED_STATE = 3,
}

export class WsAdapter extends OriginWsAdapter {
  public bindMessageHandlers(
    client: any,
    handlers: MessageMappingProperties[],
    transform: (data: any) => Observable<any>,
  ) {
    const close$ = fromEvent(client, CLOSE_EVENT).pipe(share(), first());
    const source$ = fromEvent(client, 'message').pipe(
      mergeMap((data: any) =>
        this.bindMessageHandler(data.data, handlers, transform).pipe(
          filter((result: any) => result),
        ),
      ),
      takeUntil(close$),
    );
    const onMessage = (response: any) => {
      if (client.readyState !== READY_STATE.OPEN_STATE) {
        return;
      }
      client.send(response);
    };
    source$.subscribe(onMessage);
  }

  bindMessageHandler(
    buffer: string | number | Buffer,
    handlers: MessageMappingProperties[],
    transform: (data: any) => Observable<any>,
  ) {
    try {
      const messageHandler = handlers.find(
      	// 这里的'message'值对应@SubscribeMessage传的值
        (handler) => handler.message === 'message',
      );
      const { callback } = messageHandler;
      return transform(callback(buffer));
    } catch {
      return EMPTY;
    }
  }
}

版本信息

node: 16.13.0
nestjs: 8.4.7

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* eslint-disable @typescript-eslint/no-var-requires */ const path = require("path"); const webpack = require("webpack"); // fork-ts-checker-webpack-plugin需要单独安装 const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); module.exports = { entry: "./src/main", target: "node", // 置为空即可忽略webpack-node-externals插件 externals: {}, // ts文件的处理 module: { rules: [ { test: /\.ts?$/, use: { loader: "ts-loader", options: { transpileOnly: true } }, exclude: /node_modules/ }, { test: /\.node$/, loader: "node-loader", } ] }, // 打包后的文件名称以及位置 output: { filename: "main.js", path: path.resolve(__dirname, "dist") }, resolve: { extensions: [".js", ".ts", ".json"], alias: { '@common': path.resolve(__dirname, 'src', 'common'), '@config': path.resolve(__dirname, 'src', 'config'), '@projects': path.resolve(__dirname, 'src', 'projects'), '@model': path.resolve(__dirname, 'src', 'model'), '@data': path.resolve(__dirname, 'src', 'data'), '@utils': path.resolve(__dirname, 'src', 'utils'), }, // fallback:{ // crypto: require.resolve("crypto-browserify"), // stream: require.resolve("stream-browserify"), // } }, plugins: [ // 需要进行忽略的插件 new webpack.IgnorePlugin({ checkResource(resource) { const lazyImports = [ "@nestjs/microservices", "@nestjs/microservices/microservices-module", "@nestjs/websockets/socket-module", "cache-manager", "class-validator", "class-transformer", "class-transformer/storage" ]; if (!lazyImports.includes(resource)) { return false; } try { require.resolve(resource, { paths: [process.cwd()] }); } catch (err) { return true; } return false; } }), new ForkTsCheckerWebpackPlugin() ] };
06-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值