Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

snowluma-ws

snowluma-ws 是一个面向 Node.js 的 ESM-only WebSocket 客户端 / 服务端库,底层使用 cmake-js + node-addon-api 构建原生 addon,遵循 RFC 6455。

它支持:

  • 独立端口运行 WebSocket 服务端
  • 独立 TLS 端口运行 wss:// 服务端
  • 复用已有的 Node.js http.Server
  • 复用已有的 Node.js https.Server
  • noServer + handleUpgrade 手动接管 HTTP upgrade
  • WebSocket 客户端连接 ws:// / wss://
  • 与 ws 包互通
  • 与 Node.js 内置 globalThis.WebSocket 客户端互通
  • text / binary 消息
  • fragmented message
  • permessage-deflate
  • WebSocket extensions 协商框架
  • subprotocol 自动协商响应
  • ping / pong
  • close code / reason
  • payload 边界与协议错误校验

环境要求

  • Node.js >= 18
  • npm
  • CMake
  • C++17 编译器
  • Windows 下建议安装 Visual Studio 2022 C++ 构建工具

安装与构建

npm install

安装时会执行:

cmake-js compile

也可以手动构建:

npm run build

构建产物默认位于:

build/Release/snowluma_ws.node

测试

npm test

当前测试覆盖:

  • 原生握手和 frame build / parse
  • 与 ws 包双向互通
  • 与 Node.js 内置 globalThis.WebSocket 客户端互通
  • ws:// / wss:// 双向连接测试
  • 独立端口服务端
  • 复用已有 http.Server
  • 复用已有 https.Server
  • noServer + handleUpgrade
  • permessage-deflate 与 ws 双向互通
  • subprotocol 自动协商
  • payload 长度边界:0, 1, 125, 126, 127, 255, 65535, 65536, 100000
  • 大消息
  • fragmentation
  • ping / pong
  • close code / reason
  • unmasked client frame
  • invalid UTF-8
  • unknown opcode
  • RSV bits
  • oversized control frame
  • non-minimal payload length
  • 并发连接
  • TCP 任意切包输入

快速开始

客户端

import { WebSocket } from 'snowluma-ws';

const socket = new WebSocket('ws://127.0.0.1:8080');

socket.on('open', () => {
  socket.send('hello');
});

socket.on('message', (data, isBinary) => {
  console.log(isBinary ? data : data.toString('utf8'));
});

socket.on('close', (code, reason) => {
  console.log('closed', code, reason);
});

独立端口服务端

import { WebSocketServer } from 'snowluma-ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('listening', () => {
  console.log('WebSocket server listening on', wss.address());
});

wss.on('connection', (socket, request) => {
  socket.on('message', (data, isBinary) => {
    socket.send(data, { binary: isBinary });
  });
});

独立 TLS 端口服务端(wss://)

import fs from 'node:fs';
import { WebSocketServer } from 'snowluma-ws';

const wss = new WebSocketServer({
  port: 8443,
  tls: {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem'),
  },
});

wss.on('connection', (socket) => {
  socket.send('connected over wss://');
});

复用已有 http.Server

import http from 'node:http';
import { WebSocketServer } from 'snowluma-ws';

const server = http.createServer((req, res) => {
  res.end('normal http response');
});

const wss = new WebSocketServer({ server });

wss.on('connection', (socket, request) => {
  socket.send('connected through shared http server');
});

server.listen(8080);

复用已有 https.Server

import fs from 'node:fs';
import https from 'node:https';
import { WebSocketServer } from 'snowluma-ws';

const server = https.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
}, (req, res) => {
  res.end('normal https response');
});

const wss = new WebSocketServer({ server });

wss.on('connection', (socket) => {
  socket.send('connected through shared https server');
});

server.listen(8443);

启用 permessage-deflate

默认不启用压缩。客户端和服务端都需要显式传入 perMessageDeflate。

import { WebSocket, WebSocketServer } from 'snowluma-ws';

const wss = new WebSocketServer({
  port: 8080,
  perMessageDeflate: true,
});

wss.on('connection', (socket) => {
  console.log(socket.extensions);
  socket.on('message', (data) => {
    socket.send(data, { compress: true });
  });
});

const client = new WebSocket('ws://127.0.0.1:8080', {
  perMessageDeflate: true,
});

client.on('open', () => {
  console.log(client.extensions);
  client.send('hello'.repeat(1000), { compress: true });
});

subprotocol 自动协商

服务端传入 protocols 后,会自动从客户端请求的协议列表中选择第一个服务端支持的协议,并返回 Sec-WebSocket-Protocol。

import { WebSocket, WebSocketServer } from 'snowluma-ws';

const wss = new WebSocketServer({
  port: 8080,
  protocols: ['chat.v2', 'chat.v1'],
});

wss.on('connection', (socket) => {
  console.log(socket.protocol);
});

const client = new WebSocket('ws://127.0.0.1:8080', ['chat.v3', 'chat.v2']);

client.on('open', () => {
  console.log(client.protocol);
});

noServer + handleUpgrade

适合在同一个 HTTP 端口上按 path 或业务逻辑手动路由 WebSocket。

import http from 'node:http';
import { WebSocketServer } from 'snowluma-ws';

const server = http.createServer((req, res) => {
  res.end('plain http');
});

const wss = new WebSocketServer({ noServer: true });

server.on('upgrade', (req, socket, head) => {
  if (req.url === '/ws') {
    wss.handleUpgrade(req, socket, head, (ws) => {
      wss.emit('connection', ws, req);
    });
    return;
  }

  socket.destroy();
});

wss.on('connection', (socket) => {
  socket.on('message', (data) => {
    socket.send('echo:' + data.toString('utf8'));
  });
});

server.listen(8080);

API

new WebSocket(url[, protocols][, options])

创建客户端连接。

支持参数:

  • url:ws:// 或 wss:// URL
  • protocols:可选协议数组
  • options.maxPayload:最大 payload 字节数
  • options.perMessageDeflate:是否请求 permessage-deflate
  • options.headers:额外握手请求头
  • options.socketOptions:透传给 net.connect 或 tls.connect

连接自签名证书的测试服务时,可以通过 socketOptions 传递 TLS 参数:

const socket = new WebSocket('wss://127.0.0.1:8443', {
  socketOptions: {
    rejectUnauthorized: false,
  },
});

常用事件:

  • open
  • message(data, isBinary)
  • ping(data)
  • pong(data)
  • close(code, reason)
  • error(error)

常用方法:

  • send(data[, options][, callback])
  • ping([data][, callback])
  • pong([data][, callback])
  • close([code][, reason])
  • terminate()

send() 的 options 支持:

  • binary:强制按 binary 或 text 发送
  • compress:协商 permessage-deflate 后是否压缩当前消息

new WebSocketServer(options)

创建服务端。

支持模式:

  • { port }:创建独立 HTTP server 并监听 WebSocket upgrade
  • { port, tls }:创建独立 HTTPS server 并监听 wss:// upgrade
  • { server }:复用已有 http.Server 或 https.Server
  • { noServer: true }:不监听端口,手动调用 handleUpgrade

常用 options:

  • port:监听端口
  • host:监听地址
  • server:已有 http.Server
  • noServer:手动 upgrade 模式
  • path:只接受指定 path
  • maxPayload:最大 payload 字节数
  • perMessageDeflate:是否接受 permessage-deflate
  • protocols:服务端支持的 subprotocol 列表,也可以是选择函数
  • verifyClient(info) 或 verifyClient(info, callback):握手校验
  • tls:独立端口模式下传给 https.createServer() 的 TLS 配置
  • key / cert / pfx 等 TLS 字段:也可直接放在 options 顶层

常用事件:

  • listening
  • connection(socket, request)
  • error(error)

常用方法:

  • address()
  • handleUpgrade(request, socket, head, callback)
  • close([callback])

与 ws 包兼容性

本库导出类似 ws 的表面 API:

import { WebSocket, WebSocketServer, Server } from 'snowluma-ws';

其中:

  • WebSocket 是客户端构造器
  • WebSocketServer 是服务端构造器
  • Server 是 WebSocketServer 的别名

测试中已经覆盖:

  • snowluma-ws server + ws client
  • ws server + snowluma-ws client
  • snowluma-ws server + Node.js 内置 globalThis.WebSocket client
  • snowluma-ws standalone TLS server + snowluma-ws wss:// client
  • ws secure server + snowluma-ws wss:// client
  • permessage-deflate 双向压缩互通
  • subprotocol 自动协商

原生模块职责

C++ addon 负责:

  • SHA1
  • Base64
  • Sec-WebSocket-Accept 计算
  • WebSocket frame 构建
  • WebSocket frame 流式解析
  • mask / unmask
  • payload length 校验
  • RSV bit 暴露与协商后校验

JavaScript 层负责:

  • TCP / HTTP I/O
  • client handshake
  • server upgrade handshake
  • message fragmentation 组装
  • WebSocket extensions 协商
  • permessage-deflate 压缩 / 解压
  • subprotocol 自动协商与校验
  • UTF-8 校验
  • ping / pong 自动响应
  • close handshake
  • 用户 API 与事件派发

协议说明

当前实现覆盖核心 RFC 6455 功能。

已实现:

  • text frame
  • binary frame
  • continuation frame
  • close frame
  • ping / pong frame
  • client-to-server masking 校验
  • server-to-client unmasked 校验
  • control frame 限制
  • close code 校验
  • UTF-8 文本校验
  • permessage-deflate
  • WebSocket extensions 协商
  • subprotocol 自动协商响应

暂未实现:

  • 浏览器环境打包

开发命令

npm run build
npm run rebuild
npm run clean
npm test

项目结构

snowluma-ws/
├─ CMakeLists.txt
├─ package.json
├─ src/
│  ├─ base64.cc
│  ├─ base64.h
│  ├─ bindings.cc
│  ├─ frame_builder.cc
│  ├─ frame_builder.h
│  ├─ frame_parser.cc
│  ├─ frame_parser.h
│  ├─ handshake.cc
│  ├─ handshake.h
│  ├─ sha1.cc
│  └─ sha1.h
├─ lib/
│  ├─ client.js
│  ├─ extensions.js
│  ├─ index.js
│  ├─ native.js
│  ├─ server.js
│  └─ websocket.js
└─ test/
   ├─ boundary.test.js
   ├─ interop.test.js
   └─ smoke.test.js

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages