一、响应包Sec-WebSocket-Accept字段值的计算
浏览器发送的最重要的一个请求头是Sec-WebSocket-Key,服务端程序需要根据RFC6455中的算法计算Sec-WebSocket-Accept的值。我们以浏览器发送了Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==为例,介绍如何计算响应值。
首先服务端程序要将Sec-WebSocket-Key的值与一个魔法字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”拼接到一起,得到“dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。
第二步,将上一步中合并的字符串使用sha1计算sha1值,这里如果使用PHP的sha1函数进行计算,要注意sha1的第二个参数必须显式的给出true值,否则sha1的结果是一个16进制的字符串,而不是二进制数值,其他语言如果有类似的情况也要注意,求出的结果是二进制,而不是转换后的16进制值。
第三步,将第二步中的二进制值使用base64进行编码,使用PHP计算方法如下:
base64_encode(sha1($secWebSocketKey."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
最后得出的结果为”s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”,将这个结果作为Sec-WebSocket-Accept的值,放入响应头中。
二、响应头的构成
1.HTTP状态码
握手响应的HTTP状态码为101 Switching Protocols,代表协议转换,如HTTP/1.1 101 Switching Protocols
2.Upgrade字段
Upgrade: websocket固定,代表转换为WebSocket协议,同浏览器请求头
3.Connection字段
Connection: Upgrade固定,同浏览器请求头
4.Sec-WebSocket-Accept
通过上面的方法计算出的值
5.Sec-WebSocket-Protocol
可选返回头,根据浏览器发送的子协议返回。如果浏览器发送了多个子协议,这里可以选择一个或多个进行返回,此处是子协议协商的过程
三、服务端响应步骤
- 判断Origin是否可信,WebSocket不存在跨域问题,在任何域中都可以与其他域建立WebSocket连接。但出于某些原因,我们不希望一些其他的网站连接服务器,这时可以验证Origin是否在访问源白名单中
- 通过Cookie验证身份,由于WebSocket的握手协议仍是通过HTTP协议进行的,它会向服务端发送目标域下(当前运行的WebSocket服务器)的cookie。服务端程序可以通过cookie将该socket与用户身份绑定到一起,就不需要在以后传输身份信息
- 读取Sec-WebSocket-Version的值判断协议版本,如果不是13,则需要用其他方法解析(本系列只介绍13版本,其他版本的握手及通信不在介绍范围内)
- 返回通过Sec-WebSocket-Key计算Sec-WebSocket-Accept的值
- 读取Sec-WebSocket-Protocol,根据服务器实现情况返回支持的一个或多个子协议名,使用半角逗号分隔
- 将第二节中的响应头发送给浏览器
四、关于Sec-WebSocket-Extensions
在WebSocket的请求和响应头中,还有一个可选的字段Sec-WebSocket-Extensions,目前很少使用,现将RFC6455内容翻译如下:
“Sec-WebSocket-Extensions”字段仅用在WebSocket连接握手阶段。它先由客户端(浏览器)发送到服务器,然后再由服务器传回客户端,以便协商连接过程中的协议层扩展集。
“Sec-WebSocket-Extensions”字段可能在HTTP请求中出现多次(等价于出现一次,但有多个值),但是最多只能在HTTP响应中出现一次。
五、握手阶段的错误处理
WebSocket协议中并未明确规定当服务器遇到错误(伪造)的握手请求该如何处理,你可以直接断开客户端的连接,但这样并不是比较好的方法,推荐使用下面的做法(浏览器会忽略出101之外的状态码,返回非101状态码浏览器会自动切断连接):
1.当客户端请求中缺少host、Upgrade、Connection、Sec-WebSocket-Key、Sec-WebSocket-Version时返回状态码400(语法格式错误)
2.当Origin不在可信源中时,返回403
3.当身份验证(cookie)出错时,返回401或403
4.当version或protocol不支持时,返回501(尚未实现)