← 中文资源

简体中文版本

WebSocket 类型化消息协议

在 WebSocket 之上叠加类型化消息协议:信封校验版本与类型字段,按类型路由到各自 Zod 载荷校验,违规消息关闭连接。

阅读英文原文

示例说明

裸 WebSocket 只保证字节到达,消息形态全靠双方自觉。协议信封把版本号与类型字段标准化——版本不匹配在信封层就被发现,载荷按类型分发到对应 schema 校验。收到不认识的类型或非法载荷时以协议错误状态码主动关闭,让对端明确知道违反契约,而不是带着脏数据继续跑。

技术要点

  • 信封与载荷两级校验分离
  • 版本字段为协议演进留出通道
  • 协议违规以 1002 状态码关闭

代码解读

Envelope 先校验原始 JSON 的结构性;Messages 表把类型名映射到载荷 schema,未注册类型直接拒绝。parseMessage 返回判别结果,调用方按 ok 分支处理;tick 更新消息打印行情,close 监听输出状态码与原因帮助定位断连性质。

使用建议

服务端也要实现对称的校验闭环。协议演进靠新增类型与版本协商,别改动既有载荷结构。心跳与超时要建立在协议内而不是依赖 TCP 保活。

代码示例

import { z } from 'zod';
import { WebSocket } from 'ws';

const Envelope = z.object({
  v: z.literal(1),
  type: z.string(),
  payload: z.unknown(),
});

const Messages = {
  'tick.update': z.object({ symbol: z.string(), price: z.number() }),
  'alert.raise': z.object({ level: z.enum(['warn', 'critical']), text: z.string() }),
};

type MessageType = keyof typeof Messages;

function parseMessage(raw: string) {
  const envelope = Envelope.safeParse(JSON.parse(raw));
  if (!envelope.success) return { ok: false, reason: 'bad envelope' };

  const schema = Messages[envelope.data.type as MessageType];
  if (!schema) return { ok: false, reason: 'unknown type ' + envelope.data.type };

  const payload = schema.safeParse(envelope.data.payload);
  return payload.success
    ? { ok: true, type: envelope.data.type, payload: payload.data }
    : { ok: false, reason: 'bad payload for ' + envelope.data.type };
}

const socket = new WebSocket('wss://stream.example.com/v1');

socket.on('message', (data) => {
  const parsed = parseMessage(data.toString());
  if (!parsed.ok) {
    socket.close(1002, 'protocol violation');
    return;
  }
  if (parsed.type === 'tick.update') {
    console.log(parsed.payload.symbol, parsed.payload.price);
  }
});

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