websocket客户端通过websocket构造函数第二个参数定义子协议,支持字符串或字符串数组,需符合ascii命名规范;服务端须在握手阶段解析sec-websocket-protocol头并响应匹配值,否则ws.protocol为空。

WebSocket子协议怎么在客户端定义
客户端定义子协议就是调用 WebSocket 构造函数时传入第二个参数,它不叫“设置头”,而是由浏览器自动映射为 Sec-WebSocket-Protocol 请求头。这个参数必须是字符串或字符串数组,不能是对象、数字或带空格的字符串。
常见错误现象包括:ws.protocol 始终为空、服务端收不到该字段、控制台报错 “Invalid subprotocol”。
- 单协议场景直接传字符串:
new WebSocket("wss://api.example.com", "chat") - 多协议协商传数组,按优先级排序:
new WebSocket("wss://api.example.com", ["myapp-v2", "myapp-v1"]) - 协议名只能含 ASCII 字母、数字、
-、.,禁止中文、@、/、空格等 —— 例如"auth:token-abc"合法,"auth/token"或"用户-v1"会触发浏览器拒绝 - 如果传了非法字符,Chrome 会静默忽略该参数(不报错但请求头不出现),Firefox 可能抛
SyntaxError
Sec-WebSocket-Protocol 请求头怎么被服务端读取
服务端不是“读取请求头”那么简单,而是在 WebSocket 握手阶段(即 HTTP Upgrade 请求处理时)从原始 HTTP 请求中提取该字段。不同框架方式差异大,关键点在于:不能等到 onOpen 再解析,必须在握手响应前完成鉴权或协议选择。
典型误区是以为像普通 HTTP 请求一样,在 WebSocket 连接建立后发个消息传 token —— 这完全绕过了子协议设计初衷,也失去握手期拦截非法连接的能力。
- Spring Boot +
spring-websocket:需自定义HandshakeInterceptor,重写beforeHandshake方法,从request.getHeaders().get("Sec-WebSocket-Protocol")取值 - FastAPI:通过
websocket.scope["subprotocols"]获取客户端声明的列表,再在accept()时显式指定返回值 - Node.js +
ws库:在handleProtocols钩子中接收protocols参数(字符串数组)和request对象,返回匹配的协议字符串或null拒绝 - 若服务端未在响应中返回
Sec-WebSocket-Protocol头,或返回值不在客户端列表中,浏览器仍会建连,但ws.protocol为空 —— 这是静默失败,极易被忽略
为什么用 Sec-WebSocket-Protocol 做鉴权而不是 URL 参数
因为它是唯一被浏览器原生 WebSocket API 官方允许携带的、可参与握手校验的自定义字段。URL 参数虽简单,但存在硬伤:会被代理、CDN、浏览器历史记录、服务端日志明文记录,且无法在握手阶段阻断非法连接。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
而 Sec-WebSocket-Protocol 的值在握手阶段就可用于校验,服务端可立即拒绝不合法的协议名(比如 token 格式错误、签名失效),避免后续资源占用。
- 适合放轻量鉴权信息:base64 编码后的短 token、版本号、租户 ID 等,如
"v2.tenant-abc"、"auth-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" - 不适合放敏感长 token 或原始密码 —— 协议名长度受浏览器实现限制(Chrome 约 4KB,但实际建议控制在 256 字符内)
- HTTP 代理或负载均衡器可能剥离或重写
Sec-WebSocket-Protocol头,生产环境务必验证中间件配置 - 前端不要拼接动态字符串传入构造函数,避免注入风险;应提前校验格式,再作为字面量传入
调试 Sec-WebSocket-Protocol 是否生效
别只看控制台有没有报错,重点查 Network 面板里的 WebSocket 握手帧 —— 它本质是两个 HTTP 包(Request + Response),不是真正的 WebSocket 数据帧。
打开 Chrome DevTools → Network → 找到 ws:// 或 wss:// 请求 → 点开 → 查看 Headers 标签页:
- 检查 Request Headers 中是否存在
Sec-WebSocket-Protocol,值是否与代码一致 - 检查 Response Headers 中是否存在同名字段,且值是否为客户端列表中的某一项
- 如果 Request 有而 Response 没有,说明服务端未正确返回或中间件过滤了该头
- 如果两者都有但
ws.protocol仍为空,大概率是服务端返回的值大小写不一致(协议名区分大小写)或含不可见字符(比如 BOM)
真正容易被忽略的是:子协议协商成功后,ws.protocol 是只读属性,且仅在 open 事件之后才可读取 —— 在 onopen 回调外访问它,结果一定是空字符串。










