为 7u.pl 构建通用的 WebSocket 服务器
注意:波兰语版本是原文。此翻译由人工智能生成,可能包含少量错误。

随着我们将越来越多的交互式微工具添加到 7u.pl 生态系统中,在完全分离的设备之间实时同步状态的需求应运而生。
例如:我们的 virtual-keyboard 项目必须将智能手机上的事件(“按下 Q 键”),发送到 Mac 上的浏览器,并直接发送到物理 Raspberry Pi,从而绕过两侧本地网络的限制。
与其为每个应用程序单独构建 WebSocket 逻辑(这会很快导致基础设施重复),我们决定创建一个单一的、独立的、非常“愚蠢”的通信服务器,它只做一件事,但做得完美无瑕:它监听房间(rooms)并在客户端之间广播(broadcast)消息。
“愚蠢”服务器的架构
通用性的关键是服务器端没有业务逻辑。服务器不知道什么是“键盘”、什么是“鼠标”,或者通过网络发送的 JSON 的结构是什么样的。
技术选择
我们决定使用结合了 Socket.IO 的 Node.js。 虽然原生的 WebSockets (ws) 更轻量,但 Socket.IO 内置了一个将连接组织成所谓 Rooms 的出色机制,这完美地契合了我们隔离不同应用程序的需求。此外,它还为我们免除了移动设备在连接不稳定时自动重连的麻烦。
源代码概览
整个服务器的核心仅包含 io 实例上的几个监听器:
io.on('connection', (socket) => {
// 将客户端加入虚拟房间
socket.on('join-room', (roomName) => {
socket.join(roomName);
});
// 向房间内的其他人广播消息(忽略发送者)
socket.on('broadcast', ({ roomName, eventName, payload }) => {
socket.to(roomName).emit(eventName, payload);
});
// 向房间内的所有人广播消息(包括发送者)
socket.on('broadcast-all', ({ roomName, eventName, payload }) => {
io.to(roomName).emit(eventName, payload);
});
});这就是全部。服务器接受任何对象 (payload),找到一个房间 (roomName),将其包装在开发人员指定的事件 (eventName) 中,然后将其抛给其余客户端。
CORS 安全性
尽管服务器对传输的数据本身一无所知,但我们希望确保我们的公共端点不会变成半个互联网的免费代理服务器。
实现需要严格的规则来验证 Origin 标头,以在所谓的 握手(handshake) 的最早阶段验证连接:
const io = new Server(httpServer, {
cors: {
origin: (origin, callback) => {
// 允许没有 Origin(物联网设备/脚本)
if (!origin) return callback(null, true);
// 允许:*.7u.pl 和本地环境
const allowedOrigins = [
/^https?:\/\/((.*)\.)?7u\.pl$/,
/^http:\/\/localhost(:\d+)?$/,
/^http:\/\/127\.0\.0\.1(:\d+)?$/
];
if (allowedOrigins.some((regex) => regex.test(origin))) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}
});由于使用 Regex(正则表达式)而不是死板的字符串,因此可以动态授权诸如 sklep.7u.pl 之类的域以及开发人员端口(例如,Vite 生态系统使用的 localhost:5173),而无需在每个新项目时重启并重新配置服务器。
部署与维护 (Docker)
服务器的大小只有几兆字节。Docker 镜像是基于 node:20-alpine 环境构建的。
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
EXPOSE 80
CMD ["npm", "start"]在生产机器上,该镜像隐藏在负载均衡器 (NGINX/Cloudflare) 后面,后者处理最终的 TLS/SSL 证书终止 (wss://sock.7u.pl)。这样的设置意味着新的基础设施对于未来的修改已经变得完全不可见(“透明”)。
总结与优势
将 WebSocket 的中转点("relay")移至一个单独的代码库,使我们摆脱了:
- 后端必须监听端口,仅仅是为了让“仅前端”客户端能够相互看见,
- 从头开始重构认证系统,
- 在项目中维持不必要的技术债务。
从现在起,*.7u.pl 域名下的每一个即将推出的交互式项目,只需添加五行导入代码,就能获得实时功能。这正是优秀架构的核心所在。