1.3 开始握手握手部分的设计目的就是兼容现有的基于 HTTP 的服务端组件(web 服务器软件)或者中间件(代理服务器软件)。这样一个端口就可以同时接受普通的 HTTP 请求或则 WebSocket 请求了。为了这个目的,WebSocket 客户端的握手是一个 HTTP 升级版的请求(HTTP Upgrade request):
[url=][/url]
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13[url=][/url]
为了遵循协议 ,握手中的头字段是没有顺序要求的。
跟在 GET 方法后面的 “请求标识符 Request-URI” 是用于区别 WebSocket 链接到的不同终节点。一个 IP 可以对应服务于多个域名,这样一台机器上就可以跑多个站点,然后通过 “请求标识符”,单个站点中又可以含有多个 WebSocket 终节点。
Host 头中的服务器名称可以让客户端标识出哪个站点是其需要访问的,也使得服务器得知哪个站点是客户端需要请求的。
其余的头信息是用于配置 WebSocket 协议的选项。典型的一些选项就是,子协议选项 Sec-WebSocket-Protocol、列出客户端支出的扩展 Sec-WebSocket-Extensions、源标识 Origin 等。Sec-WebSocket-Protocol 子协议选项,是用于标识客户端想和服务端使用哪一种子协议(都是应用层的协议,比如 chat 表示采用 “聊天” 这个应用层协议)。客户端可以在 Sec-WebSocket-Protocol 提供几个供服务端选择的子协议,这样服务端从中选取一个(或者一个都不选),并在返回的握手信息中指明,比如:
Sec-WebSocket-Protocol: chatOrigin可以预防在浏览器中运行的脚本,在未经 WebSocket 服务器允许的情况下,对其发送跨域的请求。浏览器脚本在使用浏览器提供的 WebSocket 接口对一个 WebSocket 服务发起连接请求时,浏览器会在请求的 Origin 中标识出发出请求的脚本所属的 ,然后 WebSocket 在接受到浏览器的连接请求之后,就可以根据其中的源去选择是否接受当前的请求。
比如我们有一个 WebSocket 服务运行在 http://websocket.example.com,然后你打开一个网页 http://another.example.com,在个 another 的页面中,有一段脚本试图向我们的 WebSocket 服务发起链接,那么浏览器在其请求的头中,就会标注请求的源为 http://another.example.com,这样我们就可以在自己的服务中选择接收或者拒绝该请求。
服务端为了告知客户端它已经接收到了客户端的握手请求,服务端需要返回一个握手响应。在服务端的握手响应中,需要包含两部分的信息。第一部分的信息来自于客户端的握手请求中的 Sec-WebSocket-Key 头字段:
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==客户端握手请求中的 Sec-WebSocket-Key 头字段中的内容是采用的 base64 编码 的。服务端并不需要将这个值进行反编码,只需要将客户端传来的这个值首先去除首尾的空白,然后和一段固定的 GUID 字符串进行连接,固定的 GUID 字符串为 258EAFA5-E914-47DA-95CA-C5AB0DC85B11。连接后的结果使用 SHA-1(160数位) 进行一个哈希操作,对哈希操作的结果,采用 base64 进行编码,然后作为服务端响应握手的一部分返回给浏览器。
比如一个具体的例子:
- 客户端握手请求中的 Sec-WebSocket-Key 头字段的值为 dGhlIHNhbXBsZSBub25jZQ==
- 服务端在解析了握手请求的头字段之后,得到 Sec-WebSocket-Key 字段的内容为 dGhlIHNhbXBsZSBub25jZQ==,注意前后没有空白
- 将 dGhlIHNhbXBsZSBub25jZQ== 和一段固定的 GUID 字符串进行连接,新的字符串为 dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11。
- 使用 SHA-1 哈希算法对上一步中新的字符串进行哈希。得到哈希后的内容为(使用 16 进制的数表示每一个字节中内容):0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea
- 对上一步得到的哈希后的字节,使用 base64 编码,得到最后的字符串s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- 最后得到的字符串,需要放到服务端响应客户端握手的头字段 Sec-WebSocket-Accept中。
服务端的握手响应和客户端的握手请求非常的类似。第一行是 HTTP状态行,状态码是 101:
HTTP/1.1 101 Switching Protocols任何其他的非 101 表示 WebSocket 握手还没有结束,客户端需要使用原有的 HTTP 的方式去响应那些状态码。状态行之后,就是头字段。
Connection 和 Upgrade 头字段完成了对 HTTP 的升级。Sec-WebSocket-Accept 中的值表示了服务端是否接受了客户端的请求。如果它不为空,那么它的值包含了客户端在其握手请求中 Sec-WebSocket-Key 头字段所带的值、以及一段预定义的 GUID 字符串(上面已经介绍过怎么由二者合成新字符串的)。任何其他的值都被认为服务器拒绝了请求。服务端的握手响应类似:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
这些字符需要被 WebSocket 的客户端(一般就是浏览器)检查核对之后,才能决定是否继续执行相应的客户端脚本,或者其他接下来的动作。
可选的头字段也可以被包含在服务端的握手响应中。在这个版本的协议中,主要的可选头字段就是 Sec-WebSocket-Protocol,它可以指出服务端选择哪一个子协议。客户端需要验证服务端选择的子协议,是否是其当初的握手请求中的 Sec-WebSocket-Protocol 中的一个。作为服务端,必须确保选的是客户端握手请求中的几个子协议中的一个:
Sec-WebSocket-Protocol: chat
服务端也可以设置 cookie 见 ,但是这不是必须的。 |