4. Opening Handshake
WebSocket 协议的 opening handshake (开启握手) 被设计为与现有 HTTP 基础设施兼容. 该握手是一个 HTTP Upgrade 请求.
4.1 客户端要求
客户端发送的握手请求必须是如下格式的有效 HTTP 请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate
必需 Header 字段
Host
- 包含服务器的主机和端口号
- 符合 HTTP/1.1 规范
Upgrade: websocket
- MUST 包含此 header
- 值为 "websocket" (大小写不敏感)
Connection: Upgrade
- MUST 包含此 header
- 表明这是一个 Upgrade 请求
Sec-WebSocket-Key
- MUST 包含此 header
- 随机生成的 16 字节值, 经过 Base64 编码
- 用于验证服务器理解 WebSocket 协议
Sec-WebSocket-Version: 13
- MUST 包含此 header
- 当前版本号为 13
可选 Header 字段
Origin
- 浏览器客户端 MUST 包含
- 非浏览器客户端 MAY 包含
- 用于服务器端安全验证
Sec-WebSocket-Protocol
- OPTIONAL
- 指定客户端支持的 subprotocol (子协议) 列表
- 多个值用逗号分隔
Sec-WebSocket-Extensions
- OPTIONAL
- 指定客户端支持的 extension (扩展)
- 格式:
extension-name [; parameter=value]*
4.2 服务器端要求
服务器必须验证客户端握手并返回适当响应.
4.2.1 读取客户端的 Opening Handshake
服务器 MUST 验证以下内容:
- HTTP method: 必须是 GET
- HTTP version: 至少为 HTTP/1.1
- Required headers: 必须存在 Host, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version
- Sec-WebSocket-Version: 必须为 13 (或服务器支持的版本)
如果验证失败, 服务器 SHOULD 返回适当的 HTTP 错误状态码.
4.2.2 发送服务器的 Opening Handshake
如果验证成功, 服务器 MUST 发送握手响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
计算 Sec-WebSocket-Accept
这是握手中的关键部分. 服务器必须按如下方式计算:
- 取客户端的
Sec-WebSocket-Key值 - 与固定 GUID 字符串拼接:
258EAFA5-E914-47DA-95CA-C5AB0DC85B11 - 对拼接后的字符串执行 SHA-1 hash
- 对 hash 结果进行 Base64 编码
算法示例 (JavaScript):
const crypto = require('crypto');
function generateAcceptKey(clientKey) {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const concatenated = clientKey + GUID;
const hash = crypto.createHash('sha1').update(concatenated).digest();
return hash.toString('base64');
}
// Example
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
const acceptKey = generateAcceptKey(clientKey);
console.log(acceptKey); // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
4.3 收集扩展和子协议
子协议协商
客户端可以在 Sec-WebSocket-Protocol header 中列出多个子协议:
Sec-WebSocket-Protocol: chat, superchat
服务器 MUST 从客户端列表中选择一个 (或不选择任何一个), 并在响应中返回它:
Sec-WebSocket-Protocol: chat
如果服务器不支持客户端请求的任何子协议, 可以省略 Sec-WebSocket-Protocol header.
扩展协商
扩展协商通过 Sec-WebSocket-Extensions header 进行:
客户端请求:
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
服务器响应:
Sec-WebSocket-Extensions: permessage-deflate
服务器可以接受扩展, 修改参数, 或拒绝扩展.
4.4 支持多个版本
如果服务器不支持客户端请求的版本, 它 SHOULD 返回 HTTP 426 Upgrade Required, 并包含一个列出受支持版本的 Sec-WebSocket-Version header:
HTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13, 8, 7
完整握手示例
成功握手
客户端 -> 服务器:
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com
服务器 -> 客户端:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
失败握手示例
不支持的版本:
HTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13
Origin 未被接受:
HTTP/1.1 403 Forbidden
安全考虑
- 验证 Origin: 服务器应验证
Originheader, 以防止跨站攻击 - 使用 TLS: 在生产环境中使用 wss://
- 验证所有 Header: 确保所有必需 header 都存在且格式正确
- 限制连接: 实现速率限制, 以防止 DoS 攻击
参考链接
- 上一章: 3. WebSocket URI
- 下一章: 5. Data Framing
- 实现示例: 握手 Key 计算