WebSocket协议中文版
摘要
WebSocket协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信。用于这个安全模型是通常由web浏览器使用的基于来源的安全模型。该协议包括一个打开阶段握手、接着是基本消息帧、TCP之上的分层(layered over TCP)。该技术的目标是为需要与服务器全双工通信且不需要依赖打开多个HTTP连接(例如,使用 XMLHttpRequest或者 <iframe>和长查询)的基于浏览器应用提供的一种机制。
本备忘录状态
这是一个Internet标准跟踪文档。
本文档是互联网工程任务组(IETF)的一个产物。它代表了IETF社区的共识。它已接受公共审查和已经被互联网工程指导委员会(IESG)认可发布。Interent标准的更多信息可以在RFC 5741的第而节找到。
本文档的当前状态信息、任何勘误表以及如何提供它的反馈,可于http://www.rfc-editor.org/info/rfc6455获得。
版权声明
版权所有(C)2011 IETF信托和确认为文档作者的人。保留所有权利。
本文档遵守BCP 78和涉及IETF文档(http://trustee.ietf.org/license-info)的在本文档发布之日起生效的IETF信托的法律条文。请仔细阅读这些文档,因为他们描述了关于本文档的你的权利和限制。从本文档中提取的代码组件必须包括描述在第四章和简体BSD License文件。e的信托法律条文并提供,不保证描述在简体BSD许可协议中。
目录
1 引言
1.1 背景
1.2 协议概述
1.3 打开阶段握手
1.4 关闭阶段握手
1.5 设计理念
1.6 安全模型
1.7 与TCP和HTTP的关系
1.8 建立连接
1.9 使用WebSocket协议的子协议
2 一致性要求
2.1 术语和其他约定
3 WebSocket URI
4 打开阶段握手
4.1 客户端要求
4.2 服务器端要求
4.2.1 读取客户端的打开阶段握手
4.2.2 发送服务器的打开阶段握手
4.3 为握手中使用的新的头字段整理的ABNF
4.4 支持多个版本的WebSocket协议
5 数据帧
5.1 概述
5.2 基本帧协议
5.3 客户端到服务器掩码
5.4 分片
5.5 控制帧
5.5.1 Close
5.5.2 Ping
5.5.3 Pong
5.6 数据帧
5.7 示例
5.8 可拓展性
6 发送和接收数据
6.1 发送数据
6.2 接收数据
7 关闭连接
7.1 定义
7.1.1 关闭WebSocket连接
7.1.2 启动WebSocket关闭阶段握手
7.1.3 关闭阶段握手已启动
7.1.4 WebSocket已关闭
7.1.5 WebSocket连接关闭代码
7.1.6 WebSocket连接关闭原因
7.1.7 失败WebSocket连接
7.2 异常关闭
7.2.1 客户端发起的关闭
7.2.2 服务端发起的关闭
7.2.3 从异常关闭中恢复
7.3 正常连接关闭
7.4 状态码
7.4.1 定义的状态码
7.4.2 保留的状态码范围
8 错误处理
8.1 处理UTF-8编码数据的错误
9 拓展
9.1 协商拓展
9.2 已知拓展
10 安全注意事项
10.1 非浏览器客户端
10.2 Origin注意事项
10.3 攻击基础设施(掩码)
10.4 实现特定限制
10.5 WebSocket客户端验证
10.6 连接的保密性和完整性
10.7 处理无效数据
10.8 使用SHA-1的WebSocket握手
11 IANA考虑
11.1 注册新的URI模式
11.1.1 注册“ws”模式
11.1.2 注册“wss”模式
11.2 注册“WebSocket”HTTP Upgrade关键字
11.3 注册新的HTTP头字段
11.3.1 Sec-WebSocket-Key
11.3.2 Sec-WebSocket-Extensions
11.3.3 Sec-WebSocket-Accept
11.3.4 Sec-WebSocket-Protocol
11.3.5 Sec-WebSocket-Version
11.4 WebSocket拓展名注册
11.5 WebSocket子协议名注册
11.6 WebSocket版本号注册
11.7 WebSocket关闭代码注册
11.8 WebSocket操作吗注册
11.9 WebSocket帧头位注册
12 其他规范使用WebSocket协议
13 致谢
14 参考资料
14.1 参考标准
14.2 参考资料
1 引言
1.1 背景
本节是非规范的。
过去,创建需要需要在客户端和服务之间的双向通信(例如,即时消息和游戏应用)的web应用,需要一个滥用的HTTP来轮询服务器进行更新但以不同的HTTP调用发生上行通知(RFC6202)。
这将导致各种各样的问题:
1、服务器被迫为每个客户端使用一些不同的底层TCP连接:一个用于发送信息到客户端和一个新的用于每个传入消息。
2、 线路层协议有较高的开销,因为每个客户端-服务器消息都有一个HTTP头信息。
3、客户端脚本被迫维护一个传出的连接到传入的连接的映射来跟踪回复。
一个简单的办法是使用单个TCP连接双向传输。这是为什么提供WebSocket协议。与WebSocket API结合[WSAPI],它提供了一个HTTP轮询的替代来进行从web页面到远程服务器的双向通信。
同样的技术可以用于各种各样的web应用:
游戏、股票行情、同时编辑的多用户应用、服务器端服务以实时暴露的用户接口等等。
WebSocket协议被设计来取代现有的使用HTTP作为传输层的双向通信技术,并受益于现有的基础设施(代理、过滤、身份验证)。这样的技术被实现来在效率和可靠性之间权衡,因为HTTP最初的目的不是用于双向通信(参见[RFC6202]的进一步讨论)。WebSocket协议试图在现有的HTTP基础设施上下文中解决现有的双向HTTP技术目标;同样,它被设计工作在HTTP端口80和443,也支持HTTP代理和中间件,即使这具体到当前环境意味着一些复杂性。但是,这种设计不限制WebSocket到HTTP,且未来的实现可以在一个专用的端口上使用一个更简单的握手,且没有再创造整个协议。最后一点是很重要的,因为交互消息的传输模式不精确地匹配标准HTTP传输并可能在相同组件上包含不常见的负载。
1.2 协议概述
本节是非规范的。
本协议有两部分:握手和数据传输。
来自客户端的握手看起来像如下形式:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
来自服务器的握手看起来像如下形式:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
来自客户端的首行遵照Request-Line格式。来自服务器的首行遵照Status-Line格式。Request-Line和Status-Line在[RFC2616]中定义。
在这两种情况中一个无序的头字段集合出现在首行之后。这些头字段的意思指定在本文档的第4章。另外的头字段也可能出现,例如cookie[RFC6265]。头的格式和解析定义在[RFC2616]。
一旦客户端和服务器都发送了它们的握手,且如果握手成功,接着开始数据传输部分。这是一个每一段都可以的双向通信信道,彼此独立,随意发生数据。
一个成功握手之后,客户端和服务器来回地传输数据,在本规范中提到的概念单位为“消息”。在线路上,一个消息是由一个或者多个帧组成。WebSocket的消息并不一定对应于一个特定的网络层帧,可以作为一个可以被一个中间件合并或分解的片段消息。
一个帧有一个相应的类型。属于相同消息的每一帧包含相同类型的数据。从广义上讲,有文本数据类型(它被解释为UTF-8[RFC3629]文本)、二进制数据类型(它的解释是留给应用)、和控制帧类型(它是不准备包含用于应用的数据,而是协议级的信号,例如应关闭连接的信号)。这个版本的协议定义了六个帧类型并保留10个以备将来使用。
1.3 打开阶段握手
本节是非规范的。
打开阶段握手的目的是兼容基于HTTP的服务器软件和中间件,以便单个端口可以用于与服务器交流的HTTP客户端和与服务器交流的WebSocket客户端。最后,WebSocket客户端的握手是一个HTTP Upgrade请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
依照[RC2616],握手中的头字段可能由客户端按照任意顺序发送,因此在接收的不同头字段中的顺序是不重要的。
"Request-URI"的GET方法[RFC2616]用于识别WebSocket连接的端点,即允许从一个IP地址服务的多个域名,也允许由单台服务器的多个WebSocket端点。
客户端按照[RFC2616]在它的握手的|Host|头字段中包含主机名,以便客户端和服务器都能验证他们同意哪一个正在使用的主机。
在WebSocket协议中另外的头字段可以用于选择选项。典型的选项在这个版本中可用的是子协议选择器(|Sec-WebSocket-Protocol|)、客户端支持的扩展列表(|Sec-WebSocket-Extensions|)、|Origin|头字段等。|Sec-WebSocket-Protocol|请求头字段可以用来表示客户端接受的子协议(WebSocket协议上的应用级协议层)。服务器选择一个可接受的协议,或不在乎它的握手中回应该值表示它已经选择了那个协议。
Sec-WebSocket-Protocol: chat
|Origin| 头字段 [RFC6454]是用于保护防止未授权的被浏览器中使用WebSocket API的脚本跨域使用WebSocket服务器。服务器收到WebSocket连接请求生成的脚本来源。如果服务器不想接受来自此源的连接,它可以选择通过发送一个适当的HTTP错误码拒绝该连接。这个头字段由浏览器客户端发送,对于非浏览器客户端,如果它在这些客户端上下文中有意义,这个头字段可以被发送。
最后,服务器要证明收到客户端WebSocket握手的信息,以便服务器不接受不是WebSocket连接的连接。这可以防止一个通过使用XMLHttpRequest [XMLHttpRequest]或一个表单提交发送它精心制作的包欺骗WebSocket服务器的攻击者。
为了证明收到的握手,服务器必须携带两条信息并组合它们并形成一个响应。第一条信息源自客户端握手中的|Sec-WebSocket-Key|头信息:
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
对于这个头字段,服务器必须携带其值(出现在头字段上,如,减去开头和结尾空格的base64编码 [RFC4648]的版本)并将这个与字符串形成的全局唯一标识符(GUID, [RFC4122]) "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"连接起来,其不太可能被不理解WebSocket协议的网络端点使用。SHA-1散列(160位)[FIPS.180-3],base64编码(参见[RFC4648]第4章)、用于这个的一系列相关事务接着再服务器握手过程中返回。
具体而言,如果在上面例子中,|Sec-WebSocket-Key|头字段的值为"dGhlIHNhbXBsZSBub25jZQ==",服务器将连接字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"形成字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。服务器使用SHA-1散列这个,并产生值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。这个值接着使用base64编码(参见[RFC4648]第4章),产生值 "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="。这个值将接着在|Sec-WebSocket-Accept|头字段中回应。
来自服务器的握手比客户端握手更简单。首行是一个HTTP Status-Line,具有状态码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 Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
这些字段由WebSocket客户端为脚本页面做检查。如果|Sec-WebSocket-Accept|不能匹配盼望的值、如果头字段缺失或者HTTP状态码不是101,则连接不能建立,且WebSocket帧将不发生。
可选的字段也可以包含在内。在这合适版本的协议中,主要可选字段是|Sec-WebSocket-Protocol|,其表示服务器选择的子协议。WebSocket客户端验证服务器包含的在WebSocket客户端握手中指定的一个值。声明多个子协议的服务器必须确保它选择第一个,基于客户端握手并指定它在其握手中。
Sec-WebSocket-Protocol: chat
服务器也可以设置cookie相关的可选字段为_set_ cookies,描述在[RFC6265]。
1.4 关闭阶段握手
本节是非规范的。
关闭阶段握手比打开阶段握手简单得多。
两个节点中的任一个都能发送一个控制帧与包含一个指定控制序列的数据来开始关闭阶段的握手(详见5.5.1节)。在收到这样一个帧时,另一个节点在响应中发送一个Close帧,如果还没有发送一个。在收到那个控制帧时,第一个节点接着关闭连接,安全地知道没有更多的数据到来。
发送一个控制帧之后,表示连接将被关闭,一个节点不会发送任何更多的数据;在接收到一个控制帧之后,表示连接将被关闭,一个节点会丢弃收到的任何更多的数据。
对于两个节点同时地初始化这个握手是安全的。
关闭阶段握手的目的是完成TCP关闭握手(FIN/ACK),基于TCP关闭阶段握手不总是可靠的端到端,尤其存在拦截代理和中间件。
通过发送一个Close帧并等待响应中的Close帧,某些情况下可避免数据不必要的丢失。例如,在某些平台上,如果一个socket关闭了,且接收队列中有数据,一个RST包被发送了,这样会导致接受RST的一方的recv()失败,即使有数据等待读取。
1.5 设计理念
本节是非规范的。
WebSocket协议应该以最小帧的原则设计(唯一存在的框架是使协议基于帧而不是基于流且支持区分Unicode文本和二进制)。期望通过应用层将元数据分层在WebSocket之上,通用的,通过应用层将元数据分层在TCP之上(例如HTTP)。
从概念上讲,WebSocket只是TCP之上的一层,执行以下操作。
1、为浏览器添加一个web基于来源的安全模型
2、添加一个寻址和协议命名机制来支持在一个IP地址的一个端口的多个主机名的多个服务。
3、在TCP之上分一个帧机制层已回到TCP基于的IP包机制,但没有长度限制。
4、包括一个额外的带内(in-band)关闭阶段握手,其被设计来工作在现存的代理和其他中间件。
除此之外,WebSocket没有添加任何东西。基本上,它的目的是尽可能接近仅暴露原始TCP到脚本,尽可能考虑到Web的约束。它也被设计为它的服务器能与HTTP服务器共享一个端口的这样一种方式,通过持有它的握手是一个有效的HTTP Upgrade请求。一个可以在概念上使用其他协议来建立客户端-服务器消息,但WebSocket的意图是提供一个相对简单的协议,可以与现有的HTTP和部署的HTTP基础设施(例如代理)同时存在,并尽可能接近TCP,且对于使用考虑到安全考虑的这样的基础设施同样是安全的,有针对性的补充以简化使用并保持简单的事情简单(如增加的消息语义)。
协议的目的是为了可拓展性:未来版本将可能引入额外的概念,如复用(multiplexing)。
1.6 安全模型
本节是非规范的。
WebSocket协议使用浏览器使用的来源模型限制web页面可以与WebSocket服务器通信,当WebSocket协议是从一个web页面使用。当然,当WebSocket协议被一个独立的客户端直接使用时(也就是,不是从浏览器中的一个web页面),来源模型不再有用,因为客户端可以提供任意随意的来源字符串。
该协议的目的是无法与现有的协议,如SMTP [RFC5321]和HTTP建立一个连接,同时允许HTTP服务器来选择支持该协议如果想要。这是通过具有严格的和详尽的握手和通过限制在握手完成之前能被插入到连接的数据(因此限制多少服务器可以被应用)实现的。
当数据是来自其他协议时,同样的目的是无法建立连接的,尤其发送到一个WebSocket服务器的HTTP,例如,如果一个HTML表单提交到WebSocket服务器可能会发生。这主要通过要求服务器验证它读取的握手来实现,它只能做如果握手包含适当的部分,只能通过一个WebSocket客户端发送。尤其是,在写本规范的时候,|Sec-|开头的字段不能由web浏览器的攻击者设置,仅能使用HTML和JavaScript APIs例如XMLHttpRequest [XMLHttpRequest]。
1.7 与TCP和HTTP的关系
本节是非规范的。
WebSocket协议是一个独立的基于TCP的协议。它与HTTP唯一的关系是它的握手是由HTTP服务器解释为一个Upgrade请求。
默认情况下,WebSocket协议使用端口80用于常规的WebSocket连接和端口443用于WebSocket连接的在传输层安全(TLS) [RFC2818]之上的隧道化。
1.8 建立连接
本节是非规范的
当一个连接到一个HTTP服务器共享的端口时(这种情况是很有可能在传输信息到端口80和443出现),连接将出现在HTTP服务器,是一个正常的具有一个Upgrade提议的GET请求。在相对简单的安装,只用一个IP地址和单台服务器用于所有的数据传输到单个主机名,这可能允许一个切实可行的办法对基于WebSocket协议的系统进行部署。在更复杂的安装(例如,负载均衡和多服务器),一组独立的用于WebSocket连接的主机从HTTP服务器分离出来可能更容易管理。在写该规范的时候,应该指出的是,在端口80和443上的连接有明显不同的成功率,对于在端口443上的连接时明显更有可能成功,尽管这可能会随着时间改变。
1.9 使用WebSocket协议的子协议
本节是非规范的。
客户端可能通过包含|Sec-WebSocket-Protocol|字段在它的握手中使用一个特定的子协议请求服务器。如果它被指定,服务器需要在它的响应中包含同样的字段和一个选择的子协议值用于建立连接。
这些子协议名字应该按照11.5节被注册。为了避免潜在的碰撞,推荐使用包含ASCII版本的子协议发明人的域名的名字。例如,如果Example公司要创建一个Chat子协议,由Web上的很多服务器实现,它们可能命名它为"chat.example.com"。如果Example组织命名它们的竞争子协议为"chat.example.org",那么两个子协议可能由服务器同时实现,因为服务器根据客户端发送的值动态地选择使用哪一个子协议。
通过改变子协议的名字,子协议可以以向后不兼容方式版本化,例如,要从"bookings.example.net" 到 "v2.bookings.example.net"。就WebSocket客户端而言,这些子协议被视为是完全不同的。向后兼容的版本可以通过重用相同的子协议字符串实现,但要仔细设置实际的子协议以支持这种可拓展性。
2 一致性要求
在本规范中所有的图表、示例和注释是非规范的,以及所有章节明确地标记为非规范的。除此之外,在本规范中的一切是规范的。
该文档中的关键字必须 "MUST", 不能"MUST NOT", 需要"REQUIRED",应当 "SHALL", 不得"SHALL NOT",应该 "SHOULD", 不应该"SHOULD NOT", 推荐"RECOMMENDED", 可能 "MAY", 和可选的"OPTIONAL" 由 [RFC2119]中的描述解释。
作为算法的一部分的祈使句中的要求措辞(例如“去掉任何前导空格字符”或“返回false并终止这些步骤”)解释为引入算法中使用的关键字("MUST","SHOULD","MAY"等)的意思。
作为算法或特定的步骤的一致性要求措辞可以以任何形式实现,只要最终结果是相等的。(尤其是,定义在本规范中的算法目的是容易遵循而不必是高性能的)。
2.1 术语和其他约定
_ASCII_指定义在[ANSI.X3-4.1986]中的字符编码方案。
此文中提到的UTF-8值和使用 UTF-8标记法格式定义在 STD 63 [RFC3629]。
关键术语例如命名算法或定义是表示像_this_。
头字段名字或变量表示像|this|。
变量值表示像/this/。
本文档提及的程序_失败WebSocket连接_。该程序定义在7.1.7节。
_将字符串转换为ASCII小写_意思是替换U+0041到 U+005A(也就是,拉丁文,大写字母A到拉丁文大写字母Z)范围的所有字符为U+0061到 U+007A(也就是,拉丁文,小写字母A到拉丁文小写字母Z)范围对应的字符。
以一个_ASCII不区分大小写_方式比较两个字符串意思是精确地比较它们,代码点对代码点,除了U+0041 到 U+005A(也就是,拉丁文,大写字母A到拉丁文大写字母Z)范围中的字符,U+0061到 U+007A(也就是,拉丁文,小写字母A到拉丁文小写字母Z)范围对应的字符被认为也匹配。
用于本文档的术语“URI”定义在[RFC3986]。
当一个实现需要_发送_作为WebSocket一部分的数据,实现可能任意地推迟实际的传输,例如,缓冲数据为了发送更少的IP包。
注意,该文档同时使用[RFC5234] 和 [RFC2616]的ABNF变体在不同章节。
3 WebSocket URIs
本规范定义了两个URI方案,使用定义在RFC 5234 [RFC5234]中的ABNF句法、和术语和由URI规范RFC 3986 [RFC3986]定义的ABNF制品。
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
host = <host, defined in [RFC3986], Section 3.2.2>
port = <port, defined in [RFC3986], Section 3.2.3>
path = <path-abempty, defined in [RFC3986], Section 3.3>
query = <query, defined in [RFC3986], Section 3.4>
端口组件是可选的;用于“WS”的默认端口是80,而用于“WSS”默认端口是443。
如果方案组件不区分大写匹配“wss”,URI被称为“安全的”(它是说,“设置了安全标记”)。
"resource-name"(在4.1节也称为/resource name/)可以通过连接以下来构造:
1、"/"如果路径组件是空
2、 "/" if the path component is empty
3、路径组件
4、the path component
5、 "?"如果查询组件是空
6、"?" if the query component is non-empty
7、查询组件
8、 the query component
片段标识符在WebSocket URI中是无意义的且必须不用在这些URI上。任何URI方法,字符“#”,当不表示片段开始时,必须被转义为%23.
4 打开阶段握手
4.1 客户端需求
要_建立WebSocket连接_,客户端打开一个连接并发送一个握手,就像本节中定义的那样。一个连接最初被定义为一个CONNECTING状态。客户端将需要提供一个/host/, /port/,/resource name/, 和 /secure/标记,它们都是在第三章讨论的WebSocket URI的组件,连同一起使用的一个/protocols/ 和 /extensions/列表。此外,如果客户端是一个web浏览器,它提供/origin/。客户端运行在一个受控环境,例如绑定到特定运营商的手机上的浏览器,可以下移(offload)连接管理到网络上的另一个代理。在这种情况下,用于本规范目的的客户端被认为包括手机软件和任何这样的代理。
当客户端要_建立一个WebSocket连接_,给定一组(/host/, /port/, /resource name/,和/secure/ 标记)、连同一起使用的一个/protocols/ 和 /extensions/列表、和在web浏览器情况下的一个/origin/,它必须打开一个连接、发送一个打开阶段握手、并读取服务器响应中的握手。应如何打开连接的确切要求、在打开阶段握手应发送什么、以及应如何解释服务器响应,在本节如下所述。在下面的文本中,我们将使用第三章中的术语,如定义在那章中的"/host/" 和 "/secure/”标记。
1. 传入该算法的WebSocket URI组件(/host/, /port/, /resource name/,和 /secure/ 标记)根据指定在第三章的WebSocket URI规范,必须是有效的。如果任何组件是无效的,客户端必须_失败WebSocket连接_并终止这些步骤。
2. 如果客户已经有一个到通过主机/host/和端口/port/对标识的远程主机(IP地址)的WebSocket连接,即使远程主机是已知的另一个名字,客户端必须等待直到连接已经建立或由于连接已经失败。必须不超过一个连接处于CONNECTING状态。如果同时多个连接到同一个IP地址,客户端必须序列化它们,以致同一时刻不多于一个的连接在以下步骤中运行。
如果客户端不能决定远程主机的IP地址(例如,因为所有通信是通过代理服务器本身进行DNS查询),那么客户端必须假定这步的目的是每一个主机名引用一个不同远程主机,且相反,客户端应该限制同时挂起的连接总数为一个适当低的数(例如,客户端可能允许到a.example.com 和 b.example.com同时挂起连接,但如果30个同时连接到同一个请求的主机,那可能是不允许的)。例如,在一个web浏览器上下文中,客户端需要考虑用户已经打开的标签数量,在设置同时挂起的连接数量的限制时。
注意:这使得它很难仅通过打开大量的WebSocket连接到远程主机为脚本执行一个拒绝服务攻击。当攻击在关闭连接之前被暂停是,服务器可以进一步降低自身的负载,因为这将降低客户端重新连接的速度。
注意:没有限制一个客户端可以与单个远程主机有的已建立的WebSocket连接数量。服务器可以拒绝接受来自具有大量的现有连接的主机IP地址的连接或当遭受高负载时断开占用资源的连接。
3. _使用代理_:当有WebSocket协议连接主机/host/和端口/port/时,如果客户端配置使用代理,那么客户端应该连接到代理并要求它打开一个倒由/host/给定主机和/port/给定端口的TCP连接。
例子:例如,如果客户端所有的信息传输使用一个HTTP代理,那么如果它视图连接到服务器example.com的端口80,它可能会发送以下行到代理服务器:
CONNECT example.com:80 HTTP/1.1
Host: example.com
如果还有密码,连接可能看起来像:
CONNECT example.com:80 HTTP/1.1
Host: example.com
Proxy-authorization: Basic ZWRuYW1vZGU6bm9jYXBlcyE=
如果客户端没有配置使用一个代理,那么应该打开一个直接TCP连接到由/host/给定的主机和/port/给定的端口。
注意:不暴露明确的UI来为WebSocket连接选择一个独立于其他代理的代理实现,鼓励使用SOCKS5 [RFC1928]代理用于WebSocket连接,如果有的话,或做不到这一点,选择为HTTPS连接配置代理胜过为HTTP连接配置代理。
为了代理自动配置脚本,传给函数的URI必须从/host/, /port/,/resource name/和/secure/标记来构造,使用第三章给定的WebSocket URI定义。
注意:WebSocket协议可以在代理自动配置脚本中从模式中识别(“WS”用于未加密的连接和“wss”用于加密的连接)。
4. 如果连接无法打开,或者因为直接连接失败或者因为任何使用的代理返回一个错误,那么客户端必须_失败WebSocket连接_并终止连接尝试。
5. 如果/secure/是true,客户端必须在连接之上执行一个TLS握手在打开连接之后和发生握手数据之前[RFC2818]。如果这个失败了(例如,服务器的证书不能被验证),那么客户端必须_失败WebSocket连接_并终止连接。否则,所有在该通道山的进一步通信必须通过加密隧道[RFC5246]。
客户端必须在TLS握手中使用服务器命名指示拓展[RFC6066]。
一旦一个到服务器的连接(包括通过代理或在TLS加密隧道之上的连接),客户端必须发送一个打开阶段握手到服务器。该握手包括一个HTTP Upgrade请求,连同一个必需的和可选的头字段列表。该握手的要求如下所示:
1. 握手必须是像[RFC2616]指定的那样的有效的HTTP请求。
2. 请求方法必须是GET、且HTTP版本必须是至少1.1。
例如,如果WebSocket URI是"ws://example.com/chat",发送的第一行应该是"GET /chat HTTP/1.1"。
3. 请求的"Request-URI"部分必须匹配定义在第三章的(一个相对URI)/resource name/或是一个绝对的http/https URI,当解析时,有一个/resource name/, /host/,和/port/匹配相应的ws/wss URI。
4. 请求必须包含一个|Host|头字段,其值包含/host/加上可选的":"后跟/port/(当没有默认端口时)。
5. 请求必须包含一个|Upgrade|头字段,其值必须包含"websocket"关键字。
6. 请求必须包含一个|Connection|头字段,其值必须包含"Upgrade"标记。
7. 请求必须包含一个名字为|Sec-WebSocket-Key|的头字段,这个头字段的值必须是临时组成的一个随机选择的已经base64编码的(参见[RFC4648]第4章)16位的值。临时必须是为每个连接随机选择的。
注意:作为一个例子,如果随机选择的值是字节序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,头字段的值必须是"AQIDBAUGBwgJCgsMDQ4PEC=="。
8. 如果请求来自一个浏览器客户端,请求必须包含一个名字为|Origin| [RFC6454]的头字段。如果连接是来自非浏览器客户端,如果该客户端的语义匹配描述在这的用于浏览器的使用情况时,请求可以包含这个字段。该头字段的值是建立正在运行的连接代码中的环境的origin的ASCII序列化。参考[RFC6454]获取如果构造该头字段的值的详细信息。
作为一个例子,如果从www.example.com下载的代码试图建立到ww2.example.com的连接,该头字段的值将是"http://www.example.com"。
9. 请求必须包含一个名字为|Sec-WebSocket-Version|的头字段。该头字段的值必须是13。
注意:尽管本文档的草案版本(-09, -10, -11,和 -12)发布了(它们多不是编辑上的修改和澄清而不是改变电报协议),值09, 10, 11,和 12不被用作有效的Sec-WebSocket-Version。这些值被保留在IANA注册中心,但并将不会被使用。
10. 请求可以包含一个名字为|Sec-WebSocket-Protocol|的头字段。如果存在,该值表示一个或者多个逗号分割的客户端要表达的子协议,按优先顺序排列,包含该值的元素必须是非空字符串,且字符在U+0021 到U+007E范围内但不包含定义在[RFC2616]中的分割字符且必须所有是唯一的字符串。用于该头字段值的ABNF是1#token,其构造和规则定义在[RFC2616]给出。
11. 请求可以包含一个名字为|Sec-WebSocket-Extensions|的头字段。如果存在,该值表示客户端想要表达的协议级的扩展。该头字段的解释和格式描述在第9.1节。
12. 请求可以包含任意其他头字段,例如,cookies [RFC6265]和/或验证相关的头字段,例如|Authorization头字段 [RFC2616],其根据定义它们的文档处理。
一旦客户端的打开阶段握手已经发送,客户端在发送任何进一步数据之前必须等待自服务器的一个响应。客户端必须验证服务器的响应,如下所示:
1. 如果收到的服务器的状态码不是101,客户端处理每个HTTP [RFC2616]程序的响应。尤其是,如果收到一个401状态码客户端可能执行身份验证;服务器可能使用一个3XX状态码重定向客户端(但客户端不需要跟随他们)等等。否则,按以下步骤处理。
2. 如果响应缺少一个|Upgrade|头字段或|Upgrade|头字段包含的值不是一个不区分大小写的ASCII匹配值"websocket",客户端必须_失败WebSocket连接_。
3. 如果想要缺少一个|Connection|头字段或者|Connection|头字段不包含一个不区分大小写的ASCII匹配值"Upgrade"符号,客户端必须_失败WebSocket连接_。
4. 如果想要缺少一个|Sec-WebSocket-Accept|头字段或 |Sec-WebSocket-Accept| 包含一个不是|Sec-WebSocket-Key|(一个字符串,不是base64编码的)与字符串 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"但忽略任何前导和结尾空格相关联的base64编码的SHA-1值,客户端必须_失败WebSocket连接_。
5. 如果响应包含一个|Sec-WebSocket-Extensions|头字段且头字段表示使用一个扩展但没有出现在客户端握手中(服务器表示的一个扩展,不是客户端请求的),客户端必须_失败WebSocket连接_。解析该头字段以确定请求了哪些扩展在9.1节讨论。
6. 如果响应包含一个|Sec-WebSocket-Protocol|头字段且该头字段表示使用一个字协议但没出现在客户端握手中(服务器表示的一个子协议,不是客户端请求的),客户端必须_失败WebSocket连接_。
如果服务器响应不符合定义在本节和4.2.2节中的服务器握手的要求,客户端必须_失败WebSocket连接_。
请注意,根据[RFC2616],所有命名在HTTP请求和HTTP响应中的头字段是不区分大小写的。
如果服务器响应验证了以上提供的,这是说,_WebSocket连接建立了_且WebSocket连接处于OPEN状态。_使用中的扩展_被定义为一个(可能为空)字符串,其值等于服务器握手中提供的|Sec-WebSocket-Extensions|头字段的值或如果在服务器握手中没有该头字段则为null值。_使用中的子协议_被定义为服务器握手中的|Sec-WebSocket-Protocol|头字段的值或如果在服务器握手中没有该头字段则为null值。另外,如果在服务器握手中表示cookie应该被设置(定义在[RFC6265])的任何头字段,这些cookie被称为_在服务器打开阶段握手期间的Cookie设置。
4.2 服务器端要求
服务器可以下移(offload)连接管理到网络上的其他代理,例如,负载均衡和反向代理。在这样的情况下,用于本规范的目的的服务器被认为是包括服务器端基础设施的所有部分,从开始的设备到终止TCP连接,处理请求和发送响应的服务器的所有方式。
例如:一个数据中心可能有一个用适当的握手来响应WebSocket请求的服务器,并按照传递连接到另一个服务器来真正处理数据帧。对于本规范的目的,“服务器”是结合了两种计算机。
4.2.1 读取客户端的打开阶段握手
当客户端开始一个WebSocket连接,它发送它的打开阶段握手部分。服务器必须至少解析这个握手为了获取必要的信息来生成服务器握手部分。
客户端打开阶段握手包括以下部分。如果服务器,当读取握手时,发现客户端没有发送一个匹配下面描述的握手(注意,按照[RFC2616],头字段顺序是不重要的),包括但不限制任何违反ABNF语法指定的握手组件,服务器必须停止处理客户端握手并返回一个具有一个适当错误码的(例如400错误的请求)HTTP响应。
1. 一个HTTP/1.1或更高版本的GET请求,包括一个"Request-URI" [RFC2616]应该被解释为定义在第3幢的/resource name/(或一个包含/resource name/的绝对HTTP/HTTPS URI)。
2. 一个|Host|头字段包含服务器的权限。
3. 一个|Upgrade|头字段包含值"websocket",视为一个不区分大小写的ASCII值。
4. 一个|Connection|头字段包含符号"Upgrade",视为一个不区分大小写的ASCII值。
5. 一个|Sec-WebSocket-Key|头字段,带有一个base64编码的值(参见[RFC4648]第4章),当解码是,长度是16字节。
6. 一个|Sec-WebSocket-Version|头字段,带有值13.
7. 可选的,一个|Origin|头字段。该头字段由所有浏览器客户端发送。一个试图缺失此头字段的连接不应该被解释为来自浏览器客户端。
8. 可选的,一个|Sec-WebSocket-Protocol|头字段,带有表示客户端想要表达的协议的值列表,按优先顺序排列。
9. 可选的,一个|Sec-WebSocket-Extensions|头字段,带有表示客户端想要表达的扩展的值列表。此头字段的解释在9.1节讨论。
10. 可选的,其他头字段,例如这些用于发送cookie或请求服务器身份验证的。未知的头字段被忽略,按照[RFC2616]。
4.2.2 发送服务器的打开阶段握手
当客户端建议一个到服务器的WebSocket连接,服务器必须完成以下步骤来接受该连接并发送服务器的打开阶段握手。
1. 如果连接发送在一个HTTPS (HTTP-over-TLS)端口上,在连接之上执行一个TLS握手。如果失败了(例如,在扩展的客户端hello "server_name"扩展中的客户端指示的一个主机名,服务器对主机不可用),则关闭该连接;否则,用于该连接的所有进一步的通信必须贯穿加密的隧道[RFC5246]。
2. 服务器可以执行额外的客户端身份认证,例如,返回401状态码与描述在[RFC2616]中的相关|WWW-Authenticate|头字段。
3. 服务器可以使用3xx状态码[RFC2616]重定向客户端。注意,此步骤可能连同,之前,或之后的可选的上面描述的身份验证步骤一起发生。
4. 建立如下信息:
/origin/
客户端握手中的|Origin|头字段表示建立连接的脚本的来源。Origin是序列化为ASCII并转换为小写。服务器可以使用这个信息作为决定是否接受传入连接的一部分。如果服务器没有验证origin,它将接受来自任何地方的连接。如果服务器不想接受这个连接,它必须返回一个适当的HTTP错误码(例如,403 Forbidden)并中断描述在本章中的WebSocket握手。更多的详细信息,请参阅第10章。
/key/
在客户端握手中的|Sec-WebSocket-Key|头字段包括一个base64编码值,如果解码,长度是16字节。这个(编码的)值用在创建服务器握手时来表示接受连接。服务器没必要使
/version/
客户端手中的|Sec-WebSocket-Version|头字段包括客户端试图通信的WebSocket协议的版本。如果该版本没有匹配服务器理解的一个版本,服务器必须中断描述在本节的WebSocket握手并替代返回一个适当的HTTP错误码(例如,426 Upgrade Required)且一个|Sec-WebSocket-Version|头字段表示服务器能理解的版本。
/resource name/
由服务器提供的服务的标识符。如果服务器提供多个服务,那么该值应该源自客户端手中的GET方法的"Request-URI"中给定的资源名。如果请求的服务器不可用,服务器必须发生一个适当的HTTP错误码(例如404 NotFound)并中断WebSocket握手。
/subprotocol/
或者一个代表服务器准备使用的子协议的单个值或者null。选择的值必须源自客户端握手,从|Sec-WebSocket-Protocol|字段具体选择一个值,服务器将使用它用于这个连接(如果有)。如果客户端握手不包含这样一个头字段或者如果服务器不同意任何客户端请求的子协议,仅接受的值为null。这个字段不存在等价于null值(意思是如果服务器不想同意任何建议的子协议,它必须在它的响应中不发送回一个|Sec-WebSocket-Protocol|头字段)。用于这些目的,空字符串与null值是不一样的,且它不是这个字段合法的值。用于该头字段的值ABNF是(符号),构造定义和规则在[RFC2616]中给出。
/extensions/
表示服务器准备使用的协议级别扩展的一个列表(可能为空)。如果服务器支持多个扩展,那么该值必须源自客户端握手,通过从|Sec-WebSocket-Extensions|字段具体地选择一个或多个值。这个字段不存在等价于null值。用于这些目的,空字符串与null值是不一样的。客户未列出的扩展必须不被列出。那些值应该被选择和解释的方法在9.1节讨论。
5. 如果服务器选择接受传入的连接,它必须以一个有效的表示以下的HTTP响应应答。
5.1一个按照RFC 2616 [RFC2616]带有101响应吗的Status-Line。这样的响应可能看起来像"HTTP/1.1 101 Switching Protocols"。
5.2 一个按照RFC 2616 [RFC2616]带有"websocket"的 |Upgrade|头字段。
5.3 一个带有"Upgrade"的|Connection|头字段。
5.4 一个|Sec-WebSocket-Accept|头字段,该头字段的值通过连接/key/构造,它定义在4.2.2节第4步,带有字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",采用SHA-1散列这个连接的值来获取一个20字节的值并base64编码(参考[RFC4648]第4章)这个20字节的散列。
该头字段的ABNF [RFC2616]定义如下:
Sec-WebSocket-Accept = base64-value-non-empty
base64-value-non-empty = (1*base64-data [ base64-padding ]) |
base64-padding
base64-data = 4base64-character
base64-padding = (2base64-character "==") |
(3base64-character "=")
base64-character = ALPHA | DIGIT | "+" | "/"
注意:例如,如果客户端握手中的|Sec-WebSocket-Key|头字段的值是"dGhlIHNhbXBsZSBub25jZQ==",服务器将追加字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"为字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
C5AB0DC85B11"形式。服务器将采取SHA-1散列这个字符串,并给出值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|头字段中被返回。
5.5 可选的,一个|Sec-WebSocket-Protocol|头字段,带有一个定义在4.2.2节第4不的值/subprotocol/。
5.6可选的,一个|Sec-WebSocket-Extensions|头字段,带有一个定义在4.2.2节第4步的值/extensions/。如果使用多个扩展,那么可以把所有都列在一个|Sec-WebSocket-Extensions|头字段中或者分配到|Sec-WebSocket-Extensions|头字段的多个实例之间。
这就完成了服务器握手。如果服务器完成这些步骤且没有中断WebSocket握手,服务器认为WebSocket连接已建立且WebSocket连接处于OPEN状态。此时,服务器可以开始发送(和接收)数据了。
4.3 为握手中使用的新的头字段整理的ABNF
本节中使用的ABNF语法/规范来自[RFC2616]第2.1节,包括“隐式*LWS规则”。
注意:以下ABNF约定于本节中。一些规则的名相当于相应的头字段名字。这样的规则表示相应的头字段值,例如Sec-WebSocket-Key ABNF规则描述了 |Sec-WebSocket-Key|头字段值的语法。在名字中带有"-Client"后缀的ABNF规则仅用在由客户端到服务器端发送请求的情况:在名字中带有"-Server"后缀的ABNF规则仅用在由服务器到客户端发生响应的情况。例如,ABNF规则Sec-WebSocket-Protocol-Client描述了客户端到服务器发送的|Sec-WebSocket-Protocol|头字段值的语法。
以下新的头字段可以在从客户端到服务器握手期间被发送:
Sec-WebSocket-Key = base64-value-non-empty
Sec-WebSocket-Extensions = extension-list
Sec-WebSocket-Protocol-Client = 1#token
Sec-WebSocket-Version-Client = version
base64-value-non-empty = (1*base64-data [ base64-padding ]) |
base64-padding
base64-data = 4base64-character
base64-padding = (2base64-character "==") |
(3base64-character "=")
base64-character = ALPHA | DIGIT | "+" | "/"
extension-list = 1#extension
extension = extension-token *( ";" extension-param )
extension-token = registered-token
registered-token = token
extension-param = token [ "=" (token | quoted-string) ]
; When using the quoted-string syntax variant, the value
; after quoted-string unescaping MUST conform to the
; 'token' ABNF.
NZDIGIT = "1" | "2" | "3" | "4" | "5" | "6" |
"7" | "8" | "9"
version = DIGIT | (NZDIGIT DIGIT) |
("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)
; Limited to 0-255 range, with no leading zeros
以下新的头字段可以在服务器到客户端握手期间被发送:
Sec-WebSocket-Extensions = extension-list
Sec-WebSocket-Accept = base64-value-non-empty
Sec-WebSocket-Protocol-Server = token
Sec-WebSocket-Version-Server = 1#version
4.4 支持多个版本的WebSocket协议
本节提供了在客户端和服务器中支持多个版本的WebSocket协议的一些指导。
使用WebSocket版本通知能力(|Sec-WebSocket-Version|头字段),客户端可以初始请求它选择的WebSocket协议的版本(这并不一定必须是客户端支持的最新的)。如果服务器支持请求的版本且握手消息是本来有效的,服务器将接受该版本。如果服务器不支持请求的版本,它必须以一个包含所有它将使用的版本的|Sec-WebSocket-Version|头字段(或多个|Sec-WebSocket-Version|头字段)来响应。此时,如果客户端支持一个通知的版本,它可以使用新的版本值重做WebSocket握手。
以下示例演示了上述的版本协商:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 25
服务器的响应可能看起来像如下:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13, 8, 7
注意服务器最后的响应可能也看起来像:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13
Sec-WebSocket-Version: 8, 7
客户端现在可以重做符合版本13的握手:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 13
5 数据帧
5.1 概述
在WebSocket协议中,数据使用帧序列来传输。为避免混淆网络中间件(例如拦截代理)和出于安全原因,第10.3节进一步讨论,客户端必须掩码它发送到服务器的所有帧(更多详细信息请参见5.3节)。(注意不管WebSocket协议是否允许在TLS之上,掩码都要做。)当收到一个没有掩码的帧时,服务器必须关闭连接。在这种情况下,服务器可能发送一个定义在7.4.1节的状态码1002(协议错误)的Close帧。服务器必须不掩码发送到客户端的所有帧。如果客户端检测到掩码的帧,它必须关闭连接。在这种情况下,它可能使用定义在7.4.1节的状态码1002(协议错误)。(这些规则可能在未来规范中放宽。)
基本帧协议定义了带有操作码的帧类型、负载长度、和用于“扩展数据”与“应用数据”以及它们一起定义的“负载数据”的指定位置。某些字节和操作码保留用于未来协议的扩展。
一个数据帧可以被客户端或者服务器在打开阶段握手完成之后和端点发送Close帧之前的任何时候传输(5.5.1节)。
5.2 基本帧协议
用于数据传输部分的报文格式是通过本节中详细描述的ABNF来描述。(注意,不像本文档的其他章节,本节中的ABNF是在位bit组上的操作。每一个位组的长度在注释中指出。在编码报文时,最重要的位是在ABNF的最左边。)下图给出了帧的高层次概述。在下图和本节后边指定的ABNF之间冲突的,这个图表时权威的。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
FIN: 1 bit
指示这个是消息的最后片段。第一个片段可能也是最后的片段。
RSV1, RSV2, RSV3: 1 bit each
必须是0,除非一个扩展协商为非零值定义含义。如果收到一个非零值且没有协商的扩展定义这个非零值的含义,接收端点必须_失败WebSocket连接_。
Opcode: 4 bits
定义了一个“负载数据”的解释。如果收到一个未知的操作码,接收端点必须_失败WebSocket连接_。定义了以下值:
* %x0 代表一个继续帧
* %x0 denotes a continuation frame
* %x1 代表一个文本帧
* %x1 denotes a text frame
* %x2 代表一个二进制帧
* %x2 denotes a binary frame
* %x3-7 保留用于未来的非控制帧
* %x3-7 are reserved for further non-control frames
* %x8 代表连接关闭
* %x8 denotes a connection close
* %x9 代表ping
* %x9 denotes a ping
* %xA 代表pong
* %xA denotes a pong
* %xB-F 保留用于未来的控制帧
* %xB-F are reserved for further control frames
Mask: 1 bit
定义是否“负载数据”是掩码的。如果设置为1,一个掩码键出现在masking-key,且这个是用于根据5.3节解掩码(unmask)“负载数据”。从客户端发送到服务器的所有帧有这个位置为1。
Payload length: 7 bits, 7+16 bits, 或者 7+64 bits
“负载数据”的长度,以字节为单位:如果0-125,这是负载长度。如果126,之后的两字节解释为一个16位的无符号整数是负载长度。如果127,之后的8字节解释为一个64位的无符号整数(最高有效位必须是0)是负载长度。多字节长度数量以网络字节顺序来表示。注意,在所有的情况下,最小数量的字节必须用于编码长度,例如,一个124字节长的字符串的长度不能被编码为序列126,0,124.负载长度是“扩展数据”长度+“应用数据”长度。“扩展数据”长度可能是零,在这种情况下,负载长度是“应用数据”长度。
Masking-key: 0 or 4 bytes
客户端发送到服务器的所有帧通过一个包含在帧中的32位值来掩码。如果mask位设置为1,则该字段存在,如果mask位设置为0,则该字段缺失。详细信息请参见5.3节 客户端到服务器掩码。
Payload data: (x+y) bytes
“负载数据”定义为“扩展数据”连接“应用数据”。
Extension data: x bytes
“扩展数据”是0字节除非已经协商了一个扩展。任何扩展必须指定“扩展数据”的长度,或长度是如何计算的,以及扩展如何使用必须在打开阶段握手期间协商。如果存在,“扩展数据”包含在总负载长度中。
Application data: y bytes
任意的“应用数据”,占用“扩展数据”之后帧的剩余部分。“应用数据”的长度等于负载长度减去“扩展数据”长度。
基本帧协议是由以下ABNF[RFC5234]正式定义的。重要的是要注意这个数据是二进制表示的,而不是ASCII字符。因此,一个1位长度的字段取值为%x0 / %x1是表示为单个为,其值为0或1,不是以ASCII编码代表字符“0”或“1”的完整字节(8位位组)。4位长度的字段值介于%x0-F之间,是通过4位表示的,不是通过ASCI字符或者这些值的完整字节(8位位组)。[RFC5234]没有指定字符编码:“规则解析为最终值的字符串,有时候被称为字符。在ABNF中,一个字符仅仅是一个非负整数。在某些上下文中,一个值到一个字符集的特定映射(编码)将被指定。”在这里,指定的编码是二进制编码,每一个最终值是编码到指定数量的比特中,每个字段是不同的。
ws-frame = frame-fin ; 1 bit in length
frame-rsv1 ; 1 bit in length
frame-rsv2 ; 1 bit in length
frame-rsv3 ; 1 bit in length
frame-opcode ; 4 bits in length
frame-masked ; 1 bit in length
frame-payload-length ; either 7, 7+16, or 7+64 bits in length
[ frame-masking-key ] ; 32 bits in length
frame-payload-data ; n*8 bits in length, where n >= 0
frame-fin = %x0 ; more frames of this message follow
/ %x1 ; final frame of this message
; 1 bit in length
frame-rsv1 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv2 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv3 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; frame continuation
frame-opcode-non-control= %x1 ; text frame
/ %x2 ; binary frame
/ %x3-7
; 4 bits in length,
; reserved for further non-control frames
frame-opcode-control = %x8 ; connection close
/ %x9 ; ping
/ %xA ; pong
/ %xB-F ; reserved for further control
; frames
; 4 bits in length
frame-masked = %x0
; frame is not masked, no frame-masking-key
/ %x1
; frame is masked, frame-masking-key present
; 1 bit in length
frame-payload-length = ( %x00-7D )
/ ( %x7E frame-payload-length-16 )
/ ( %x7F frame-payload-length-63 )
; 7, 7+16, or 7+64 bits in length,
; respectively
frame-payload-length-16 = %x0000-FFFF ; 16 bits in length
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 64 bits in length
frame-masking-key = 4( %x00-FF )
; present only if frame-masked is 1
; 32 bits in length
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; when frame-masked is 1
/ (frame-unmasked-extension-data
frame-unmasked-application-data)
; when frame-masked is 0
frame-masked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-masked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
frame-unmasked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-unmasked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
5.3 客户端到服务器掩码
一个掩码的帧必须有5.2节定义的字段frame-masked设置为1。
掩码键完全包含在帧中,5.2节定义的frame-masking-key。它用于掩码定义在相同章节的frame-payload-data中的“负载数据”,其包含“扩展数据”和“应用数据”。
掩码键是由客户端随机选择的32位值。当准备一个掩码的帧时,客户端必须从允许的32位值集合中选择一个新的掩码键。掩码键需要是不可预测的;因此,掩码键必须来自一个强大的熵源,且用于给定帧的掩码键必须不容易被服务器/代理预测用于后续帧的掩码键。掩码键的不可预测性对防止恶意应用的作者选择出现在报文上的字节是必要的。RFC 4086 [RFC4086]讨论了什么需要一个用于安全敏感应用的合适熵源。
掩码不影响“负载数据”的长度。变换掩码数据到解掩码数据,或反之亦然,以下算法被应用。相同的算法应用,不管转化的方向,例如,相同的步骤即应用到掩码数据也应用到解掩码数据。
变换数据的八位位组i是原始数据的八位位组异或i取模4位置的掩码键的八位位组:
j = I MOD 4
transformed-octet-I = original-octet-I XOR masking-key-octet-j
负载长度,在帧中以frame-payload-length表示,不包括掩码键的长度。它是“负载数据”的长度,例如,跟在掩码键后边的字节数。
5.4 分片
分片的主要目的是运行当消息开始但不必缓冲该消息时发送一个未知大小的消息。如果消息不能被分片,那么端点将不得不缓冲整个消息以便在首字节发生之前统计出它的长度。对于分片,服务器或中间件可以选择一个合适大小的缓冲,当缓冲满时,写一个片段到网络。
第二个分片的用例是用于多路复用,一个逻辑通道上的一个大消息独占输出通道是不可取的,因此多路复用需要可以分割消息为更小的分段来更好的共享输出通道。(注意,多路复用扩展在本文档中没有描述。)
除非另有扩展规定,帧没有语义含义。一个中间件可能合并并且/或分割帧,如果客户端和服务器没有协商扩展;或如果已协商了一些扩展,但中间件理解所有协商的扩展且知道如何去合并且/或分割在这些扩展中存在的帧。这方面的一个含义是,在没有扩展的情况下,发送者和接收者必须不依赖于特定帧边界的存在。
以下规则应用到分片:
1、一个没有分片的消息由单个带有FIN位设置(5.2节)和一个非0操作码的帧组成。
2、一个分片的消息是单个带有FIN为清零(5.2节)和一个非0操作码的帧组成,跟随零个或多个带有FIN为清零和操作码设置为0的帧,且终止于一个带有FIN为设置且0操作码的帧。一个分片的消息概念上是等价于单个大的消息。其复杂是等价于按顺序串联片段的负载;然而,在存在扩展的情况下,这个可能不适用扩展定义的“扩展数据”存在的解释。例如:“扩展数据”可能仅在首个片段开始处存在且应用到随后的片段,或“扩展数据”可以存在于仅用于到特定片段的每个片段。在没有“扩展数据”的情况下,以下例子展示了分片如何工作。
例子:对于一个作为三个片段发送的文本消息,第一个片段将有一个0x1操作码和一个FIN位清零,第二个片段将有一个0x0操作码和一个FIN位清零,且第三个片段将有0x0操作码和一个FIN位设置。
3、控制帧(参见5.5节)可能被注入到一个分片消息的中间。控制帧本身必须不被分割。
4、消息分片必须按发送者发送顺序交付给收件人。
5、片段中的一个消息必须不能与片段中的另一个消息交替,除非已协商了一个能解释交替的扩展。
6、一个端点必须能处理一个分片消息找那个间的控制帧。
7、一个发送者可以为非控制消息创建任何大小的片段。
8、 客户端和服务器必须支持接收分片和非分片的消息。
9、由于控制帧不能被分片,一个中间件必须不尝试改变控制帧的分片。
10、如果使用了任何保留的位置且这些值的意思对中间件是未知的,一个中间件必须不改变一个消息的分片。
11、在一个连接的上下文中,已经协商了扩展且中间件不知道协商的扩展的语义,一个中间件必须不改变任何消息的分片。同样,没有看见WebSocket握手(且没有被通知有关它的内容)、导致一个WebSocket连接的一个中间件,必须不改变这个连接的任何消息的分片。
12、由于这些规则,一个消息的所有分片是相同类型的,以第一个片段的操作码设置。因为控制帧不能被分片,用于一个消息中的所有分片的类型必须是文本、或者二进制、或者一个保留的操作码。
注意:如果控制帧不能被插入,一个ping延迟,例如,如果跟着一个大消息将是非常长的。因此,要求在分片消息的中间件处理控制帧。
实现注意:在没有任何扩展时,一个接收者不必按顺序缓冲整个帧来处理它,例如,如果使用了一个流式API,一个帧的一部分能被交付到应用。但是,请注意这个假设可能不适用所有未来的WebSocket扩展。
5.5 控制帧
控制帧由操作码确定,其中操作码最重要的位是1.当前定义的用于控制帧的操作码包括0x8 (Close), 0x9 (Ping),和0xA (Pong)。操作码0xB-0xF保留用于未来尚未定义的控制帧。
控制帧用于传达有关WebSocket的状态。控制帧可以插入到分片消息的中间。
所有的控制帧必须有一个125字节的负载长度或者更少,必须不被分段。
5.5.1 关闭
关闭帧包含0x8操作码。
关闭帧可以包含内容体(帧的“应用数据”部分)指示一个关闭的原因,例如端点关闭了、端点收到的帧太大、或端点收到的帧不符合端点期望的格式。如果有内容体,内容体的头两个字节必须是2字节的无符号整数(按网络字节顺序)代表一个在7.4节的/code/值定义的状态码。跟踪2字节的整数,内容体可以包含UTF-8编码的/reason/值,本规范没有定义它的解释。数据不必是人类可读的但可能对调试或传递打开连接的脚本相关的信息是有用的。由于数据不保证人类可读,客户端必须不把它显示给最终的用户。
客户端发送到服务器的关闭帧必须根据5.3节被掩码。
在应用发送关闭帧之后,必须不发送任何更多的数据帧。
如果一个端点接收到一个关闭帧且先前没有发送一个关闭帧,端点必须在响应中发送一个关闭帧。(当在响应中发生关闭帧时,端点通常回送它接收到的状态码)它应该根据实际情况尽快这样做。端点可以延迟发送关闭帧直到它当前消息发送了(例如,如果一个分片消息的大多数已经发送了,端点可以发送剩余的片段在发送一个关闭帧之前)。但是,不保证一个已经发送关闭帧的端点将继续处理数据。
发送并接收一个关闭消息后,一个端点认为WebSocket连接关闭了且必须关闭底层的TCP连接。服务器必须立即关闭底层TCP连接,客户端应该等待服务器关闭连接但可能在发送和接收一个关闭消息之后的任何时候关闭连接,例如,如果它没有在一个合理的时间周期内接收到服务器的TCP关闭。
如果客户端和服务器同时都发送了一条关闭消息,两个端点都将发送和接收一个关闭消息且应该认为WebSocket连接关闭了并关闭底层TCP连接。
5.5.2 Ping
Ping帧包含0x9操作码。
Ping帧可以包含“应用数据”。
当接收到一个ping帧时,一个端点必须在响应中发送一个Pong帧,除非它早已接收到一个关闭帧。它应该尽可能快地以Pong帧响应。Pong帧在5.5.3节讨论。
一个端点可以在连接建立之后并在连接关闭之前的任何时候发送一个Ping帧。
注意:一个Ping可以充当一个keepalive,也可以作为验证远程端点仍可响应的手段。
5.5.3 Pong
Pong帧包含一个0xA操作码。
5.5.2节详细说明了应用Ping和Pong帧的要求。
一个Pong帧用来回应一个Ping帧时,必须包含一份在接收到的Ping帧的消息体中完全相同的“应用数据”。
如果端点接收到一个Ping帧但是对于前一个Ping帧还没有返回相应的Pong帧,端点可以选择仅为最近处理的Ping帧发送一个Pong帧。
一个Pong帧可以自发的进行发送,充当单向的心跳。接收到一个自发的Pong帧时不需要回应一个Pong帧。
5.6 数据帧
数据帧(例如,非控制帧)由操作码最高位是0的操作码标识。当前为数据帧定义的操作码包括0x1 (文本), 0x2 (二进制)。 操作码 0x3-0x7保留用于未来尚未定义的非控制帧。
数据帧携带应用层和/或扩展层数据。操作码决定了数据的解释:
Text
“负载数据”是编码为UTF-8的文本数据。注意,一个特定的文本帧可能包括部分UTF-8序列;不管怎么样,整个消息必须包含有效的UTF-8。重新组装的消息中无效的UTF-8的处理描述在8.1节。
Binary
“负载数据”是随意的二进制数据,其解释仅仅是在应用层。
5.7 示例
1、未掩码文件消息的单个帧
* 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello")
2、掩码的文本消息的单个帧
* 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
(contains "Hello")
3、一个分片的未掩码的文本消息
* 0x01 0x03 0x48 0x65 0x6c (contains "Hel")
* 0x80 0x02 0x6c 0x6f (contains "lo")
4、未掩码的Ping请求和掩码的Ping响应
* 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含内容体“Hello”,但内容体的内容是随意的)
* 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58(包含内容体“Hello”,匹配ping的内容体)
5、单个未掩码帧中的256字节的二进制消息
* 0x82 0x7E 0x0100 [256字节的二进制数据]
6、单个未掩码帧中的64KB的二进制消息
* 0x82 0x7F 0x0000000000010000 [65536字节的二进制数据]
5.8 可拓展性
协议被设计为允许扩展,这将增加功能到基础协议。端点的一个连接必须在打开阶段握手期间协商使用的任何扩展。本规范提供了用于扩展的操作码0x3 到 0x7 和0xB 到 0xF、“扩展数据”字段、和帧-rsv1、帧-rsv2、和帧-rsv3帧头位。9.1节进一步讨论了扩展协商。以下是一些预期使用的扩展。这个列表是不完整的也是不规范的。
1、“扩展数据”可以放置在“负载数据”中的“应用数据”之前。
2、保留的位可以分配给需要的每一个帧。
3、保留的操作码值能被定义。
4、如果需要更多的操作码值,保留的位可以分配给操作码字段。
5、一个保留的位或一个“扩展”操作码可以定义以从“负载数据”中分配额外的位来定义更大的操作码或更多的每位帧位。
6 发送和接收数据
6.1 发送数据
为了_发送一个WebSocket消息_,其中包括WebSocket连接之上的/data/,端点必须执行以下步骤。
1. 端点必须确保WebSocket连接处于OPEN状态(比较4.1节和4.2.2节)。如果在任何时候WebSocket连接的状态改变了,端点必须终止以下步骤。
2. 端点必须封装/data/到定义在5.2节的一个WebSocket帧。如果要发送的数据太大或如果在端点想要开始发生数据时数据作为一个整体不可用,端点可以按照5.2节的定义交替地封装数据到一系列的帧中。
3. 第一个包含数据的帧的操作码(帧-opcode)必须按照5.2节的定义被设置为适当的值用于接收者解释数据是文本还是二进制数据。
4. 包含数据的最后帧的FIN位(帧-FIN)必须按照5.2节的定义设置为1。
5. 如果数据正由客户端发送,帧必须按照5.3节的定义被掩码。
6. 如果任何扩展(第9章)已经协商用于WebSocket连接,额外的考虑可以按照这些扩展定义来应用。
7. 已成型的帧必须在底层网络连接之上传输。
6.2 接收数据
为了接收WebSocket数据,端点监听底层网络连接。传入数据必须按照5.2节的定义解析为WebSocket帧。如果接收到一个控制帧(5.5节),帧必须按照5.5节的定义来处理。当接收到一个数据帧时(5.6节),端点必须注意5.2节由操作码(帧-opcode)定义的数据的/type/。这个真的“应用数据”被定义为消息的/data/。如果帧由一个未分片的消息组成(5.4节),这是说_已经接收到一个WebSocket消息_,其类型为/type/且数据为/data /。如果帧时一个分片消息的一部分,随后数据帧的“应用数据”连接在一起形成/data /.当接收到由FIN位(帧-fin)指示的最后片段时,这是说_已经接收到一个WebSocket消息_,其数据为/data/(由连续片段的“应用数据”组成)且其类型为/type/(分配消息的第一个帧指出)。随后的数据帧必须被解释为属于一个新的WebSocket消息。
扩展(第9章)可以改变数据如何读的语义,尤其包括什么组成一个消息的边界。扩展,除了在负载中的“应用数据”之前添加“扩展数据”外,也可以修改“应用数据”(例如压缩它)。
服务器必须按照5.3节的定义为从客户端接收到的数据帧移除掩码。
7 关闭连接
7.1 定义
7.1.1 关闭WebSocket连接
为_关闭WebSocket_连接_,端点需关闭底层TCP连接。端点应该使用一个方法完全地关闭TCP连接,以及TLS会话,如果合适,丢弃任何可能已经接收的尾随的字节。当必要时端点可以通过任何可用的手段关闭连接,例如当受到攻击时。
底层TCP连接,在大多数正常情况下,应该首先被服务器关闭,所以它持有TIME_WAIT状态而不是客户端(因为这会防止它在2个报文最大生存时间(2MSL)内重新打开连接,然而当一个新的带有更高的seq number的SYN时没有对应的服务器影响TIME_WAIT连接被立即重新打开)。在异常情况下(例如在一个合理的时间量后没有接收到服务器的TCP Close)客户端可以发起TCP Close。因此,当服务器被指示_关闭WebSocekt连接_,它应该立即发起一个TCP Close,且当客户端被指示时也这么做时,它应该等待服务器的一个TCP Close。
例如一个如何使用Berkeley sockets在C中得到完全地关闭的例子,一端会在socket上以SHUT_WR调用shutdown(),调用recv()直到获得一个指示那个节点也已经执行了一个有序关闭的0返回值,且最终在socket上调用close()方法。
7.1.2 启动WebSocket关闭阶段握手
为了_启动WebSocket关闭阶段握手_,其带有一个状态码(7.4节)/code/和一个可选的关闭原因(7.1.6节)/reason/,一个端点必须按照5.5.1节的描述发送一个Close控制帧,其状态码设置为/code/且其关闭原因设置为/reason/。一旦一个端点已经发送并接收到一个Close控制帧,哪个端点应该按照7.1.1节的描述_关闭WebSocket连接_。
7.1.3 WebSocket关闭阶段握手已启动
一旦发送或接收到一个Close控制帧,这就是说,_WebSocket关闭阶段握手已启动_,且WebSocket连接处于CLOSING状态。
7.1.4 WebSocket已关闭
当底层TCP连接已关闭,这就是说_WebSocket连接已关闭_且WebSocket连接处于CLOSED状态。如果TCP连接在WebSocket关闭阶段我是已经完成后被关闭,WebSocket连接被说成已经_完全地_关闭了。
如果WebSocket连接不能被建立,这就是说,_ WebSocket 连接关闭了_,但不是_完全的_。
7.1.5 WebSocket连接关闭代码
按照5.5.1 和 7.4节的定义,一个Close控制帧可以包含一个表示关闭原因的状态码。一个正关闭的WebSocket连接可以同时由两个端点初始化。_WebSocket连接Close Code_定义为包含在由实现该协议的应用接收到的第一个Close控制帧的状态码(7.4节)。如果这个Close控制帧不包含状态码,_WebSocket连接Close Code_被认为是1005。如果_WebSocket连接已关闭_且端点没有接收到Close状态码(例如可能发生在底层传输连接丢失时),_WebSocket连接Close Code_被认为是1006。
注意:两个端点可以有不一致的_WebSocket连接关闭代码_。例如,如果远程端点发送了一Close帧,但本地应用还没有从它的socket接收缓冲区中读到包含Close帧的数据,且本地应用独立地决定关闭连接和发送一个Close帧,两个端点都将发送和接收Close帧且将不发送更多的Close帧。每一个端点将看见另一端发送的以_WebSocket连接关闭代码_结束的状态码。例如,在两个端点独立且在大致相同的时间同时_开启WebSocket关闭阶段握手_的情况下,两个端点可以有不一致的_WebSocket连接关闭代码_是可能的。
7.1.6 WebSocket连接关闭原因
按照5.5.1 和 7.4节的定义,一个Close控制帧可以包含一个指示关闭原因的状态码,接着是UTF-8编码的数据,上述数据留给断点解释且本协议没有定义。WebSocket连接的关闭可以被任何一个端点初始化,可能同时发生。_WebSocket连接关闭原因_由跟在包含在实现该协议的应用接收到的第一个Close控制帧状态码(7.4节)后边的UTF-8编码的数据定义。如果Close控制帧中没有这样的数据,_WebSocket连接关闭原因_是空字符串。
注意:按照7.1.5节指出的相同的逻辑,两个端点可以有不一致的_WebSocket连接关闭原因_。
7.1.7 失败WebSocket连接
某些算法和规范要求端点_失败WebSocket 连接_。要做到这一点,客户端必须_关闭WebSocket 连接_,并可以以适当的方式把问题报告给用户(这将对开发人员非常有用的)。同样的,为了做到这一点,服务器必须_关闭WebSocket 连接_,并应该记录下问题。
如果_已建立的WebSocket连接_在端点需要_失败WebSocket 连接_之前,端点应该在处理_关闭WebSocket 连接_之前发送一个带有适当状态码的Close帧(7.4节)。如果端点认为另一边不太可能接收到并处理关闭帧可以省略发送一个关闭帧,因为错误的性质,导致WebSocket连接失败摆在首要位置。端点必须在被指示为_失败WebSocket 端点_之后不继续尝试处理来自远程端点的数据(包括响应关闭帧)。
除了上边指出的或由应用层指定的(例如,使用WebSocket API的脚本),客户端应该关闭连接。
7.2 异常关闭
7.2.1 客户端发起的关闭
某些算法,尤其在打开阶段握手期间,需要客户端_失败WebSocket 连接_。为了做到这一点,客户端必须按照7.1.7节定义的那样_失败WebSocket 连接_。
如果在任何时候,底层的传输层连接意外丢失,客户端必须_失败WebSocket 连接_。
除了上边指出的或由应用层指定的(例如,使用WebSocket API的脚本),客户端应该关闭连接。
7.2.2 服务端发起的关闭
某些算法需要或推荐服务端在打开阶段握手期间_中断WebSocket 连接_。为了做到这一点,服务端必须简单地_关闭WebSocket 连接_(7.1.1节)。
7.2.3 从异常关闭中恢复
异常关闭可能由任何原因引起。这样的关闭可能是一个瞬时错误导致的,在这种情况下重新连接可能导致一个好的连接和一个重新开始的正常操作。这样的关闭也可能是一个非瞬时问题导致的,在这种情况下如果每个部署的客户端遇到异常关闭并立即且持续地尝试重新连接,服务端可能会因为大量的客户端尝试重新连接遇到的拒绝服务攻击。这种情况的最终结果可能是服务不能及时恢复或恢复是更加困难。
为了避免这个,当客户端遇到本节描述的异常关闭之后尝试重新连接时,应该使用某种形式的补偿。
第一个重新连接尝试应该延迟一个随机的时间量。这种随机延迟的参数的选择留给客户端决定;一个可随机选择的值在0到5秒是一个合理的初始延迟,不过客户端可以选择不同的间隔由于其选择一个延迟长度基于实现经验和特定的应用。
第一次重新连接尝试失败,随后的重新连接尝试应该延迟递增的时间量,使用的方法如截断二进制指数退避算法。
7.3 正常连接关闭
服务端在需要是可能关闭WebSocket连接。客户端不能随意关闭WebSocket连接。在这两种情况下,端点通过如下过程_开始WebSocket 关闭握手_初始化一个关闭(7.1.2节)。
7.4 状态码
当关闭一个已经建立的连接(例如,当在打开阶段握手已经完成后发送一个关闭帧),端点可以表明关闭的原因。由端点解释这个原因,并且端点应该给这个原因采取动作,本规范是没有定义的。本规范定义了一组预定义的状态码,并指定哪些范围可以被扩展、框架和最终应用使用。状态码和任何相关的文本消息是关闭帧的可选的组件。
7.4.1 定义的状态码
当发送关闭帧时端点可以使用如下预定义的状态码。
1000:1000表示正常关闭,意思是建议的连接已经完成了。
1001:1001 表示端点“离开”,例如服务器关闭或浏览器导航到其他页面。
1002:1002 表示端点因为协议错误而终止连接。
1003:1003 表示端点由于它收到了不能接收的数据类型(例如,端点仅理解文本数据,但接收到了二进制消息)而终止连接。
1004:保留。可能在将来定义某具体的含义。
1005:1005 是一个保留值,且不能由端点在关闭控制帧中设置此状态码。它被指定用在期待一个用于表示没有状态码是实际存在的状态码的应用中。
1006:1006 是一个保留值,且不能由端点在关闭控制帧中设置此状态码。它被指定用在期待一个用于表示连接异常关闭的状态码的应用中。
1007:1007 表示端点因为消息中接收到的数据是不符合消息类型而终止连接(比如,文本消息中存在非UTF-8 [RFC3629]数据)。
1008:1008 表示端点因为接收到的消息违反其策略而终止连接。这是一个当没有其它合适状态码(例如1003或1009)或如果需要隐藏策略的具体细节时能被返回的通用状态码。
1009:1009 表示端点因接收到的消息对它的处理来说太大而终止连接。
1010:1010 表示端点(客户端)因为它期望服务器协商一个或多个扩展,但服务器没有在WebSocket握手响应消息中返回它们而终止连接。所需要的扩展列表应该出现在关闭帧的/reason/部分。注意,这个状态码不能被服务器端使用,因为它可以使WebSocket握手失败。
1011:1011 表示服务器端因为遇到了一个不期望的情况使它无法满足请求而终止连接。
1015:1015是一个保留值,且不能由端点在关闭帧中被设置为状态码。它被指定用在期待一个用于表示连接由于执行TLS握手失败而关闭的状态码的应用中(比如,服务器证书不能验证)。
7.4.2 保留的状态码范围
0-999:0-999 范围内的状态码不被使用。
1000-2999:1000-2999 范围内的状态码保留给本协议、其未来的修订和一个永久的和现成的公共规范中指定的扩展的定义。
3000-3999:3000-3999 范围内的状态码保留给库、框架和应用使用。这些状态码直接向IANA注册。本规范未定义这些状态码的解释。
4000-4999:4000-4999 范围内的状态码保留用于私有使用且因此不能被注册。这些状态码可以被在WebSocket应用之间的先前的协议使用。本规范未定义这些状态码的解释。
8 错误处理
8.1 处理UTF-8编码数据的错误
当一个端点解析字节流为UTF-8数据,但发现字节流实际上不是一个有效的UTF-8流,那么端点必须_失败WebSocket连接_。这条规则则应用在打开握手期间和随后的数据交换期间。
9 扩展
WebSocket客户端可以请求本规范的扩展,且WebSocket服务器可以接受一些或所有客户端请求的扩展。服务器不必响应不是客户端请求的任何扩展。如果扩展参数包含在客户端和服务器之间的协商中,这些参数必须按照参数应用到的扩展规范来选择。
9.1 协商扩展
客户端通过包含一个|Sec-WebSocket- Extensions|头字段请求扩展,其按照正常的HTTP头字段规则(参考[RFC2616],4.2节)并且头字段的值按照以下ABNF定义[RFC2616]。注意本章使用的ABNF语法/规则来源于[RFC2616]包括“隐式的*LWS规范”。如果客户端或服务器在协商阶段接收到的值不符合下边的ABNF,这种畸形数据的接收人必须立即_失败WebSocket连接_。
Sec-WebSocket-Extensions = extension-list
extension-list = 1#extension
extension = extension-token *( ";" extension-param )
extension-token = registered-token
registered-token = token
extension-param = token [ "=" (token | quoted-string) ]
当使用引用字符串的语法变种是,引用字符串之后的值必须符合token' ABNF.
注意,像其他HTTP头字段,这个头字段可以跨多个行分割或组合,因此,以下是等价的:
Sec-WebSocket-Extensions: foo
Sec-WebSocket-Extensions: bar; baz=2
完全等价于
Sec-WebSocket-Extensions: foo, bar; baz=2
所有使用的extension-token必须是一个registered token(参考11.4节)。任何给定扩展提供的参数必须被扩展定义。注意,客户端只需要提供使用任何公布的扩展,除非服务器表示它希望使用扩展,否则必须使用它们。
注意:扩展的的顺序是重要的。在多个扩展间的相互作用可以定义在定义扩展的文档中。在没有这样定义的情况下,解释是它请求中的客户端列出的头字段表示一个它希望使用的头字段的偏好,第一个列出的选项是最优选的。服务器在响应中列出的扩展表示扩展是实际政治用于连接的扩展。扩展应该修改数据和/或组帧,数据的操作顺序应该假定是与打开阶段握手期间服务器响应中列出的扩展顺序是一样的。
例如,如果有两个扩展"foo" 和 "bar" ,且如果服务器发送的头字段|Sec-WebSocket-Extensions|有值"foo, bar" ,那么数据上的操作将变为 bar(foo(data)),是更改数据本身(如压缩)或更改可能“堆叠”的组帧。
可接受的扩展头字段(注意:为了可读性,将折叠较长行)的非规范例子:
Sec-WebSocket-Extensions: deflate-stream
Sec-WebSocket-Extensions: mux; max-channels=4; flow-control,
deflate-stream
Sec-WebSocket-Extensions: private-extension
服务器通过包含一个容纳了一个或多个扩展的客户端请求的|Sec-WebSocket-Extensions|头字段来接受一个或多个扩展。所有扩展参数的解释,和什么构成一个有效的
9.2 已知扩展
扩展提供了一种机制来实现选择性加入的附加协议特性。本文档没有定义任何扩展。但实现可以使用单独定义的扩展。
10 安全注意事项
本章描述了一些适用于WebSocket协议的安全注意事项。具体的安全注意事项在本章的子章节描述。
10.1 非浏览器客户端
WebSocket协议防止恶意的JavaScript运行在一个受信任的应用内部,比如web浏览器,例如,通过检查头字段|Origin|(见下文)。更多细节请参考1.6节。在一个更强大的客户短的情况下,这样的假设不成立。
虽然该协议的目的是被web页面中的脚本使用,它也可以被主机直接使用。这样的主机按照它们自己的行为行事,因此可以发送伪造的|Origin|头字段,骗过服务器。因此服务器应该小心,假设它们是直接与来自已知源的脚本通信,且必须考虑到它们可能以非预期方式访问。尤其,服务器不应该相信任何输入是有效的。
例如:如果服务器使用输入作为SQL查询的一部分,所有输入文本在传输到SQL服务器之前应该被转义,以免服务器收到SQl注入攻击。
10.2 Origin注意事项
服务器不打算处理来自任意web页面的输入,但仅限于网站应该验证|Origin|头是一个它们盼望的源。如果源指示时服务器不可接受的,那么它应该以一个包含HTTP 403 Forbidden的状态码的回复响应WebSocket握手。
当不受信任方通常是一个执行在受信任的客户端上下文中的JavaScript应用的作者时,|Origin|头字段可以保护攻击的情况。客户端本身可以与服务器联系,并通过|Origin|头字段机制,决定是否提供JavaScript应用的这些通信权限。目的不是为了阻止非浏览器建立连接,而是确保受信的浏览器在潜在的恶意JavaScript控制下不能伪造WebSocket握手。
10.3 攻击基础设施(掩码)
除了端点是WebSockets攻击的目标之外,web基础设施的其他部分,如代理,也可能是攻击的对象。在本协议正在开发时,进行了一个实验旨在演示一类代理上的攻击,其导致部署在野的缓存代理中毒。一般的攻击形式是在“攻击者”的控制下建立一个到服务器的连接,执行类似于WebSockets协议建立连接的HTTP连接上的UPGRADE,且随后在已经UPGRADE的连接上发送数据,看起来像一个GET请求一个特定的已知资源(在一次攻击中,很可能会像广泛部署的用于跟踪点击或一个广告服务网络资源的脚本)。远程服务器响应的东西看起来像是到伪造的GET请求的一个响应,且这个响应将被一个非零百分比的部署的中间件缓存,因此使缓存中了毒了。这种攻击的静效应将是如果说服用户去访问攻击者控制的网站,攻击者可能使用户和其他晚于相同缓存的用户的缓存中毒且在其他源运行恶意脚本,影响网络安全模型。
为避免这种部署的中间件上的攻击,前置不兼容HTTP的和帧一起的应用提供的数据是不够的,因为无法详尽地发现和测试每一个不符合中间件的不跳过这样的非HTTP帧和错误地假装帧负载。因此,采用的防御是掩码所有从客户端到服务端发送的数据,使远程脚本(攻击者)无法控制数据如何在电线上发送,从而无法构造一个可能被中间件误解释的作为一个HTTP请求的消息。
客户端必须为每一帧选择一个新的掩码密钥,使用一个不能被提供数据的终端应用预测。例如,每次掩码可以从一个强加密的随机数生成器获取。如果使用相同的密钥或存在一个可预测的模式用于选择下一个密钥,当掩码后,攻击者可以发送一个消息,可能作为一个HTTP请求出现(通过交换消息,攻击者希望观察线上并用下一个将被使用的掩码密钥掩码它,当客户端应用它时掩码密钥将有效的解码数据)。
一旦从客户端传输一个帧已经开始,帧的负载(应用提供的数据)必须不能被应用修改也是必要的。
否则,攻击者可能发送一个已知初始数据(如都是0)的长帧,一收到数据的第一部分后就开始计算使用的掩码密钥,当掩码后,接着修改尚未发送的作为一个请求出现的帧中的数据(这本质上是和前面段落描述的使用一个已知的或可预测的掩码密钥是相同的问题)。
如果需要发送额外的数据或要发送的数据以某种方式修改了,新的修改了的数据必须在一个新的帧中发送,那么需要一个新的掩码密钥。总之,一旦开始传输一个帧,对于远程脚本(应用)来说,内容必须不能是可修改的。
威胁模型用来保护客户端发送的在一个请求出现的数据。因此,需要掩码的信道是从客户端到服务器的数据。服务器到客户端的数据可以作出看起来像一个响应,但为了完成这个请求,客户端也必须有能力去伪造一个请求。因此,没必要在两个方向上掩码数据(从客户端到服务器的数据没有掩码)。
尽管掩码提供了保护,对于客户端和服务器没有掩码的这种类型的中毒攻击,非兼容的HTTP代理将依然是脆弱的。
10.4 实现特定限制
实现已经实现-和/或特定平台的有关帧大小或总消息大小的限制,从多个帧重新组装后,必须保证它们自己不超过这些限制。(例如,一个恶意终端无论是通过单一的大帧(例如,2**60大小)还是通过发送一个长流的分片消息的一部分的小帧,可以设法耗尽它的对等体端点(Peer,即要攻击的那一方)的内存或安装一个拒绝服务攻击)。这样的实现应该对帧大小和从多个帧重组后的总消息大小加以限制。
10.5 WebSocket客户端验证
本规范没有规定任何特定的方式在WebSocket握手期间服务器可以验证客户端。WebSocket服务器可以使用任何客户端对普通HTTP服务器可用的验证机制,如Cookie,HTTP验证,或者TLS验证。
10.6 连接的保密性和完整性
连接的保密性和完整性是通过运行在TLS (wss URIs)上的WebSocket协议提供的。WebSocket实现必须支持TLS并应该在它们的对等端点通信时使用它。
对于使用TLS的连接,TLS提供的受益量在很大程度上取决于在TLS握手期间协商的算法强度。例如,一些TLS加密机制不提供连接的保密性。为了实现合理级别的包含,客户端应该仅适用强TLS算法。Web安全上下文:用户接口指南[W3C.REC-wsc-ui-20100812]讨论了什么构成强TLS算法。[RFC5246]的附录 A.5 和附录D.3中提供了额外的指导。
10.7 处理无效数据
传入的数据必须始终由客户端和服务器验证。如果,在任何时候,一个端点不理解它的数据或违反了一些端点确定的安全输入标准,或当端点看到一个打开阶段握手没有符合它期望的值(例如,在客户端请求中不正确的路径或源),端点可以终止TCP连接。如果在WebSocket握手成功后接收到了无效数据,端点应该在进行_关闭WebSocket连接_之前发送一个带有适当状态码(7.4节)的关闭帧。使用一个带有适当状态码的关闭帧能帮助诊断问题。如果在WebSocket握手期间发送了无效的数据,服务器应该返回一个适当的HTTP[RFC2616]状态码。使用错误的编码发送文本数据是通常出现的一类安全问题。本协议规定一个Text数据类型(而不是Binary或其他类型)的消息包含UTF-8编码的数据。虽然仍指定了长度,且实现本协议的应用应该使用长度来决定帧从哪真正的结束,但以一个不当的编码发送数据仍可能打破建立在本协议之上的应用的假设,导致从误解释数据到丢失数据或潜在的安全漏洞。
10.8 使用SHA-1的WebSocket握手
本文档中描述的WebSocket 握手不依赖于任何SHA-1安全特性,例如碰撞性或抗第二前像攻击(如同[RFC4270]中的描述)。
11 IANA考虑
11.1 注册新的URI模式
11.1.1 注册"ws"模式
一个|ws| URI标识一个WebSocket服务器和资源名称。
URI模式名称:ws
状态: 永久的
URI模式语法:使用ABNF [RFC5234]语法和URI规范[RFC3986]的ABNF终结符:
"ws:" "//" authority path-abempty [ "?" query ]
<path-abempty> 和 <query> [RFC3986]组件形成的资源名发生给服务器来确定服务期望的类型。其他组件的含义描述在[RFC3986]。
URI模式语义:这个模式的作用仅是使用WebSocket协议打开一个连接。
编码考虑:上边定义的语法不包括host组件中的字符,必须按照[RFC3987]从Unicode转换为ASCII或其替换。为了模式标准化的目的,国际化域名(IDN)形式的host组件和它们转换的域名代码被认为是等价的(参考[RFC3987]5.3.3节)。
上边定义的语法不包括其他组件中的字符,必须按照定义在URI [RFC3986]和国际化资源标识符(IRI) [RFC3986]规范从Unicode编码转换为ASCII,通过首先编码字符为UTF-8,接着使用它们百分数编码的形式替换相应的字节。
应用/协议使用这个URI模式命名:WebSocket协议
互操作性考虑:使用WebSocket需要使用HTTP版本1.1或更高
安全考虑:参考“安全考虑”章节
联系方式: HYBI WG hybi@ietf.org
作者/变更管理员: IETF iesg@ietf.org
参考资源:RFC 6455
11.1.2 注册"wss"模式
一个|wss| URI标识一个WebSocket服务器和资源名称,并标明在受TLS保护的连接之上的通信(包括标准的TLS的好吃,比如数据保密性和完整性和端点认证)。
URI模式名称:wss
状态:永久的
URI模式语法:使用ABNF [RFC5234]语法和URI规范[RFC3986]的ABNF终结符:
"wss:" "//" authority path-abempty [ "?" query ]
<path-abempty> 和 <query>[RFC3986]组件形成的资源名发生给服务器来确定服务期望的类型。其他组件的含义描述在[RFC3986]。
URI模式语义:这个模式的作用仅是使用WebSocket协议打开一个使用TLS的连接。
编码考虑:上边定义的语法不包括host组件中的字符,必须按照[RFC3987]从Unicode转换为ASCII或其替换。为了模式标准化的目的,国际化域名(IDN)形式的hsot组件和它们转换的域名代码被认为是等价的(参考[RFC3987]5.3.3节)。
上边定义的语法不包括其他组件中的字符,必须按照定义在URI [RFC3986]和国际化资源标识符(IRI)[RFC3987]规范从Unicode编码转换为ASCII,通过首先编码字符为UTF-8,接着使用它们百分数编码的形式替换相应的字节。
应用/协议使用这个URI模式命名:WebSocket协议
互操作性考虑:使用WebSocket需要使用HTTP版本1.1或更高。
安全考虑:参考“安全考虑”章节
联系方式: HYBI WG <hybi@ietf.org>
作者/变更管理员: IETF <iesg@ietf.org>
参考资源: RFC 6455
11.2 注册“WebSocket”HTTPUpgrade关键字
本节按照RFC 2817 [RFC2817]定义了在HTTP Upgrade符号注册中心注册一个关键字。
符号名称:WebSocket
作者/变更管理员:IETF <iesg@ietf.org>
联系方式:HYBI <hybi@ietf.org>
参考资源: RFC 6455
11.3 注册新的HTTP头字段
11.3.1 Sec-WebSocket-Key
本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。
头字段名:Sec-WebSocket-Key
使用协议:http
状态:标准
作者/变更管理员:IETF
参考资源:RFC 6455
相关信息:该头字段仅用于WebSocket打开阶段握手。
|Sec-WebSocket-Key|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器,提供部分信息用于服务器检验它收到了一个有效的WebSocket握手。这有助于确保服务器不接收正被滥用来发送数据给毫不知情的WebSocket服务器的非WebSocket客户端的连接(例如HTTP客户端)。
|Sec-WebSocket-Key|头字段在一个HTTP请求中不能出现多于一个。
11.3.2 Sec-WebSocket-Extensions
本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。
头字段名:Sec-WebSocket-Extensions
使用协议:http
状态:标准
作者/变更管理员:IETF
参考资源:RFC 6455
相关信息:该头字段仅用于WebSocket打开阶段握手。
|Sec-WebSocket-Extensions|头字段用于WebSocket打开阶段握手。它最初是从客户端发送到服务器,随后从服务器端发送到客户端,用来达成在整个连接阶段的一组协议级扩展。
|Sec-WebSocket-Extensions|头字段在HTTP请求中可以出现多次(逻辑上等价于单个|Sec-WebSocket-Extensions|头字段包含的所有值)。但是,|Sec-WebSocket-Extensions|头字段在一个HTTP响应中必须不出现多于一次。
11.3.3 Sec-WebSocket-Accept
本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。
头字段名:Sec-WebSocket-Accept
使用协议:http
状态:标准
作者/变更管理员:IETF
规范文档: RFC 6455
相关信息:该头字段仅用于WebSocket打开阶段握手。
|Sec-WebSocket-Accept|头字段用于WebSocket打开阶段握手。它从服务器发送到客户端来确定服务器愿意启动WebSocket连接。
|Sec-WebSocket-Accept|头在一个HTTP响应中必须不出现多于一次。
11.3.4 Sec-WebSocket-Protocol
本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。
头字段名:Sec-WebSocket-Protocol
使用协议:http
状态:标准
作者/变更管理员:IETF
规范文档:RFC 6455
相关信息:该头字段仅用于WebSocket打开阶段握手。
|Sec-WebSocket-Protocol|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器端,并从服务器端发回到客户端来确定连接的子协议。这使脚本可以选择一个子协议和确定服务器同一服务子协议。
|Sec-WebSocket-Protocol|头字段在一个HTTP请求中可以出现多次(逻辑上等价于|Sec-WebSocket-Protocol|头字段包含的所有值)。但是,|Sec-WebSocket-Protocol|头字段在一个HTTP响应必须不出现多于一次。
11.3.5. Sec-WebSocket-Version
本节描述了在永久消息头字段命名注册中心[RFC3864]中注册一个头字段。
头字段名:Sec-WebSocket-Version
使用协议:http
状态:标准
作者/变更管理员:IETF
规范文档:RFC 6455
相关信息:该头字段仅用于WebSocket打开阶段握手。
|Sec-WebSocket-Version|头字段用于WebSocket打开阶段握手。它从客户端发送到服务器端来指定连接的协议版本。这能使服务器正确解释打开阶段握手和发送数据的随后数据。如果服务器不能以安全的方式解释数据则关闭连接。当从客户端接收到不匹配服务器端理解的版本是,WebSocket握手错误,|Sec-WebSocket-Version|头字段也从服务器端发送到客户端。在这种情况下,头字段包括服务器端支持的协议版本。
注意:如果没有期望更高版本号,必然是向下兼容低版本号。
|Sec-WebSocket-Version|头字段在一个HTTP响应中可以出现多次(逻辑行等价于单个|Sec-WebSocket-Version|透过自动包含的所有值)。但是,|Sec-WebSocket-Version|头字段在HTTP请求中必须不出现多于一次。
11.4 WebSocket扩展名注册
本规范根据RFC 5226 [RFC5226]陈述的原则,创建一个新的IANA注册用于与WebSocket协议一起使用的WebSocket扩展名。
作为本注册的一部分,IANA维护以下信息:
扩展标识符:扩展标识符,将被用在注册到本规范11.3.2节的|Sec-WebSocket-Extensions|头字段。其值必须符合定义在本规范9.1节的扩展-符号要求。
扩展通用名称:拓展名称,通常称为扩展。
拓展定义:在扩展用于的WebSocket协议中定义了文档参考。
已知的不兼容扩展:与此扩展是不兼容的一个扩展标识符列表。
WebSocket扩展名受制于“先来先服务”的IANA注册策略[RFC5226]。
在此注册中心没有初始值。
11.5 WebSocket子协议名注册
本规范根据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于与WebSocket协议一曲使用的WebSocket子协议名。
作为本注册的一部分,IANA维护以下信息:
子协议标识符:子协议标识符,将被用在注册到本规范11.3.4节的 |Sec-WebSocket-Protocol|头字段。其值必须符合定义在本规范4.1节给出的第10条的符号要求--也就是,其值必须是RFC 2616 [RFC2616]定义的一个符号。
子协议通用名称:子协议名称,通常称为子协议
子协议定义:在子协议用于定义了WebSocket协议的文档参考
WebSocket子协议名受制于“先来先服务”的IANA注册策略[RFC5226]。
11.6 WebSocket Version Number Registry WebSocket版本号注册
本规范依据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于与WebSocket协议一起使用的WebSocket版本号。
作为注册的一部分,IANA维护以下信息:
版本号: 用于|Sec-WebSocket-Version|的版本号指定在本规范4.1节。其值必须是一个在0到255(包括)之间的非负整数。
参考:RFC请求一个新的版本号或带版本号的草案名称(见下文)。
状态:“临时的”或“标准的”。参考下面的说明。
一个版本号被临时指定为“临时的”或“标准的”。
“标准的”版本号是记录在一个RFC中并用来识别一个主要的、稳定的WebSocket协议版本,例如本RFC定义的版本。“标准的”版本号受制于“IETF评审“IANA注册策略。[RFC5226].
"Interim"的版本号记录在一个Internet草案中用并用于帮助实现这识别和与部署的WebSocket版本互操作,例如在公布这个RFC之前指定的版本。“临时的”版本号受制于“专家评审”IANA注册策略[RFC5226],HYBI工作组主席(或,如果工作组关闭了,IETF应用区域的区域董事)将是初始的指定专家。
IANA已经添加如下的初始值到注册中心:
+--------+-----------------------------------------+----------+ |Version | Reference | Status | | Number | | | +--------+-----------------------------------------+----------+ | 0 + draft-ietf-hybi-thewebsocketprotocol-00 | Interim | +--------+-----------------------------------------+----------+ | 1 + draft-ietf-hybi-thewebsocketprotocol-01 | Interim | +--------+-----------------------------------------+----------+ | 2 + draft-ietf-hybi-thewebsocketprotocol-02 | Interim | +--------+-----------------------------------------+----------+ | 3 + draft-ietf-hybi-thewebsocketprotocol-03 | Interim | +--------+-----------------------------------------+----------+ | 4 + draft-ietf-hybi-thewebsocketprotocol-04 | Interim | +--------+-----------------------------------------+----------+ | 5 + draft-ietf-hybi-thewebsocketprotocol-05 | Interim | +--------+-----------------------------------------+----------+ | 6 + draft-ietf-hybi-thewebsocketprotocol-06 | Interim | +--------+-----------------------------------------+----------+ | 7 + draft-ietf-hybi-thewebsocketprotocol-07 | Interim | +--------+-----------------------------------------+----------+ | 8 + draft-ietf-hybi-thewebsocketprotocol-08 | Interim | +--------+-----------------------------------------+----------+ | 9 + Reserved | | +--------+-----------------------------------------+----------+ | 10 + Reserved | | +--------+-----------------------------------------+----------+ | 11 + Reserved | | +--------+-----------------------------------------+----------+ | 12 + Reserved | | +--------+-----------------------------------------+----------+ | 13 + RFC 6455 | Standard | +--------+-----------------------------------------+----------+
11.7. WebSocket关闭代码注册
本规范依据RFC 5226 [RFC5226]陈述的原则,创建了一个新的IANA注册用于WebSocket关闭代码。
作为本注册的一部分,IANA维护以下信息:
状态码:状态码表示一个按照本文档7.4节的WebSocket连接关闭的原因。状态时一个在1000到4999(包括)之间的一个整数数字。
含义:状态码的含义。每一个状态码都必须有唯一的含义。
联系方式:保留状态代码实体的联系方式。
参考:稳定的文档要求状态码并定义它们的含义。在1000-2999范围内的状态码是必须的且推荐的状态码在3000-3999范围内。
WebSocket关闭代码根据它们的范围受不同的注册要求。本协议请求使用的状态码和其后续版本或拓展受制于“标准功能”、“规定要求”(这意味着“指定专家”)或“IESG审查”IANA注册策略中的任何一个,且应该允许在1000-2999范围内。库、框架和应用请求使用的状态码受制于“先来先服务”IANA注册策略且应该允许在3000-3999范围内。4000-4999范围的状态码被指定用于私有使用。请求应该指出他们要求的状态码是用于WebSocket协议(或未来版本的协议)、扩展,或库/框架/应用。
IANA已经添加如下初始值到注册中心:
|Status Code | Meaning | Contact | Reference | -+------------+-----------------+---------------+-----------| | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1001 | Going Away | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1002 | Protocol error | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 | | | payload data | | | -+------------+-----------------+---------------+-----------| | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------| | 1011 | Internal Server | hybi@ietf.org | RFC 6455 | | | Error | | | -+------------+-----------------+---------------+-----------| | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 | -+------------+-----------------+---------------+-----------|
11.8 WebSocket操作码注册
本规范根据RFC 5226 [RFC5226]陈述的原则,创建一个新的IANA注册用于WebSocket操作码。
作为本注册的一部分,IANA维护以下信息:
操作码:操作码表示WebSocket帧的帧类型,定义在5.2节。操作码是一个0到15(包括)之间的数字。
含义:状态码值的含义。
参考:规范要求的操作码。
WebSocket状态码受制于“标准功能”IANA注册策略[RFC5226]。
IANA已经添加了如下的初始值到注册中心:
|Opcode | Meaning | Reference | -+--------+-------------------------------------+-----------| | 0 | Continuation Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 1 | Text Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 2 | Binary Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 8 | Connection Close Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 9 | Ping Frame | RFC 6455 | -+--------+-------------------------------------+-----------| | 10 | Pong Frame | RFC 6455 | -+--------+-------------------------------------+-----------|
11.9 WebSocket帧头位注册
本规范依据RFC 5226 [RFC5226] 陈述的原则,创建了一个新的IANA注册用于WebSocket 帧头位。此注册控制的位分别标记为5.2节的RSV1,RSV2和 RSV3。
这些位被保留用于未来版本或本规范的扩展。
WebSocket帧头位分配受制于“标准功能”IANA注册策略[RFC5226]。
12 其他规范使用WebSocket协议
WebSocket协议的目的是被另一个规范使用来提供一个通用机制来动态作者-定义内容,例如,在一个规范中定义一个脚本API。
这样的规范首先需要_建议一个WebSocket连接_,该算法是:
1、目的地,包含一个/host/和一个 /port/。
2、一个 /resource name/,运行在一个host和port标识多个服务。
3、一个 /secure/标记,如果连接时加密的则为true,否则为false。
4、一个源[RFC6454]的ASCII序列化,负责连接。
5、可选的,一个字符串标识一个协议,层叠在WebSocket连接之上。
/host/、 /port/、 /resource name/和 /secure/标记通常从一个URI中使用该步骤解析一个WebSocket URI组件获得。如果没有指定一个WebSocket,则这些步骤失败。
如果在任何时候连接将被关闭,那么规范需要使用_关闭WebSocket 连接_算法(7.1.1节)。
7.1.4节定义了什么时候_ WebSocket 连接关闭_。
当打开一个连接,规范将需要处理什么时候_已经接收了一个WebSocket 消息_的情况(6.2节)。
要发送一些数据/data/到一个打开的连接,规范需要_发送一个WebSocket 消息_(6.1节)
13 致谢
略
14参考资料
略
The WebSocket Protocol [RFC6455],张开涛[译],部分内容有修改。