WebSocket 协议 RFC 文档(中文翻译)
2019-10-22 14:47:20 阿炯

建议:对照英文版一起阅读。

摘要( Abstract )
第一章--介绍( Introduction )
第二章--一致性要求( Conformance Requirements )
第三章--WebSocket网址( WebSocket URIs )
第四章--连接握手( Opening Handshake )
第五章--数据帧(Data Framing)
第六章--发送与接收消息(Sending and Receiving Data)
第七章--关闭连接(Closing the Connection)
第八章--错误处理(Error Handling)
第九章--扩展(Extension)
第十章--安全性考虑(Security Considerations)
第十一章--IANA 注意事项(IANA Considerations)
第十二章--使用其他规范中的WebSocket协议
第十三章--致谢(略)
第十四章--参考文献(略)



0.摘要及版权声明


概述

本系列内容为RFC6455 WebSocket协议的中文翻译版。进行相关文档规范的翻译初衷是为了更加深刻的了解WebSocket以及相关内容。

摘要
翻译版包含了部分个人的理解,大部分内容为直译,其他小部分内容可能为意译,适合有兴趣的同学进行了解和学习。如果希望对整个WebSocket协议有具体的了解,建议对照的英文文档进行阅读。如果有翻译上的错误,也欢迎大家指出。

摘要
WebSocket协议能够通过在受控的环境中运行不可信代码的客户端与已选择通信的远端主机基于该不可信代码进行双向交流。这个用于WebSocket的安全模型是复用Web浏览器使用的基于Origin的安全模型(origin-based security model,可以参考此处)。这个协议由一个开放的握手过程组成,其次是基于TCP的基本数据帧。这个技术的目标是提供基于浏览器的应用与服务端进行双向通行的机制,而不需要通过多个HTTP连接(例如使用XMLHttpRequest或者Iframe模拟长轮询)。

备忘录状态
这是一个互联网标准跟踪文档。这个文档是由互联网工程任务组(IETF,Internet Engineering Task Force)产出的。它代表了互联网工程任务组社区的共识。这个文档已经征求过公众的意见并且互联网工程指导小组(IESG,Internet Engineering Steering Group)已经同意发布。更多关于互联网标准的信息在RFC 5741的第二节可以看到。关于这篇文档当前状态的信息和勘误表,以及如何进行反馈可以在此处查看。

版权声明
2011年授权给IETF授信和被认为是文档作者的人。保留所有权利。这个文档适用于BCP 78和IETF之前的相关IETF文档都在此文档发布日期生效。请细心阅读这些文档,他们说明了对于这篇文档你的权利和限制。从此文档中提取的代码组件必须包含如第四节所述的法律规定的简化的BSD许可协议文本。

第一章--介绍(Introduction)

概述

本文为WebSocket协议的第一章,本文翻译的主要内容为针对整个WebSocket进行一个简单而又全面的介绍。通过这篇文章我们能够对WebSocket有一个整体的大致了解。

1.1.背景

过去,创建需要在客户端和服务之间双向通信(例如,即时消息和游戏应用)的web应用, 需要一个滥用的HTTP来轮询服务器进行更新但以不同的HTTP调用发生上行通知[RFC6202]。

这将导致各种各样的问题:
服务器被迫为每个客户端使用一些不同的底层TCP连接: 一个用于发送信息到客户端和一个新的用于每个传入消息。

线路层协议有较高的开销,因为每个客户端-服务器消息都有一个HTTP头信息。

客户端脚本被迫维护一个传出的连接到传入的连接的映射来跟踪回复。

一个简单的办法是使用单个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章。另外的头字段也可能出现,例如cookies[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

依照[RFC2616],握手中的头字段可能由客户端按照任意顺序发送,因此在接收的不同头字段中的顺序是不重要的。

“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]、base-64编码(参见[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之上的一层,执行以下操作:
o 为浏览器添加一个web 基于来源的安全模型

o 添加一个寻址和协议命名机制来支持在一个IP地址的一个端口的多个主机名的多个服务

o 在TCP之上分一个帧机制层以回到TCP基于的IP包机制,但没有长度限制

o 包括一个额外的带内(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“表单”提交到WebScoket服务器可能会发生。这主要通过要求服务器验证它读取的握手来实现,它只能做 如果握手包含适当的部分,只能通过一个WebScoket客户端发送。尤其是,在写本规范的时侯,|Sec-|开头的字段不能由web浏览器的攻击者设置,仅能使用HTML和JavaScript API,例如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客户端而言,这些子协议将被视为是完全不同的。向后兼容的版本可以通过重用相同的子协议字符串实现,但要仔细设置实际的子协议以支持这种可扩展性。

总结

本文通过对WebSocket进行了一个全面的大致介绍,能够让大家对于WebSocket相关协议内容有一个初步的理解。


第二章--一致性要求(Conformance Requirements)

概述

本文为WebSocket协议的第二章,本文翻译的主要内容为WebSocket协议中相关术语的介绍。

在本规范中所有图表、示例、和注释是非规范的,以及所有章节明确地标记为非规范的。除此之外,在本规范中的一切是规范的。

该文档中的关键字“必须(MUST)”、“不能(MUST NOT)”、“需要(REQUIRED)”、“应当(SHALL)”、“不得(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可能(MAY)”、和“可选的(OPTIONAL)”由[RFC2119]中的描述解释。

作为算法一部分的祈使句中的要求措辞(例如 “去掉任何前导空格字符” 或 “返回false并终止这些步骤” )解释为引入算法中使用的关键字("MUST", "SHOULD", "MAY"等)的意思。

作为算法或特定的步骤的一致性要求措辞可以(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一部分的数据,实现可能(MAY)任意地推迟实际的传输,例如,缓冲数据为了发送更少的IP包。注意,该文档同时使用[RFC5234]和[RFC2616]的ABNF变体在不同章节。


第三章--WebSocket网址(WebSocket URIs)


本规范定义了两个URI方案,使用定义在RFC5234[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/)可以通过连接以下来构造:

"/" 如果路径组件是空
 路径组件
"?" 如果查询组件是非空
查询组件

片段(译者注:# Fragment)标识符在WebSocket URI中是无意义的且禁止在这些URI上。任何URI方案,字符“#”,当不表示片段开始时,必须被转义为%23。


第四章--连接握手(Opening Handshake)

概述

本文为WebSocket协议的第四章,本文翻译的主要内容为WebSocket建立连接开始握手的内容,主要包含了客户端和服务端握手的内容,以及双方如何处理相关字段和逻辑。

4.1 客户端要求

为了建立一个WebSocket连接,客户端需要建立一个连接并且发送一个在本节中定义的握手协议。连接最初状态为CONNECTING。客户端需要提供一个第三章讨论过的主机(host)、端口(port)、资源名称(resource name)和安全标记(secure)字段以及可被使用的一个协议(protocol)和扩展(extensions)列表。另外,如果客户端是一个Web浏览器,还需要提供源(origin)字段。
客户端在一个受控制的环境内运行,如使用特定运营商的手机浏览器,可能会断开连接切换到其他的运营商。在这种情况下,我们需要考虑包括手机软件和相关运营商在内的指定客户端。
当客户端通过一系列的配置字段(主机(host)、端口(port)、资源名称(resource name)和安全标记(secure))以及一个可被使用的协议(protocol)和扩展(extensions)列表来建立一个WebSocket连接,它一定会通过发送一个握手协议,并且受到一个服务端的握手响应来建立一条连接。建立连接具体需要哪些东西,在开始握手的时候会发送哪些字段,如何处理解读服务端的的响应都会在这一部分得到解答。在下面的内容中,我们会使用到第三章定义的一些术语如主机(host)和安全(secure)字段。

1.WebSocket的URI部分传递的字段(主机(host)、端口(port)、资源名称(resource name)和安全标记(secure))必须是在第三章WebSocket URIs部分指定过的有效字段,如果任意部分是无效字段,那么客户端一定会在接下来的步骤中关闭连接。

2.如果客户端有一条通过远端主机(IP地址)定义的主机和端口定义的已经建立连接的WebSocket连接,即使这个远端主机被定义为了其他的名字,这个客户端也必须等到当前的这条连接建立成功或者失败才能建立连接。客户端最多有一条连接可以处于CONNECTING状态。如果多个连接尝试同时与一个相同的IP地址建立连接,客户端必须把他们进行排序,所以只能有一个连接执行下面的步骤。
如果客户端不能够确定远程主机的IP地址(例如所有的请求都通过一个自己执行DNS查询的代理),那么客户端必须基于此假设每一个主机名都对应着不同的远端主机,因此客户端应该限制同时连接的总数目在一个比较合理的小数目上(例如:客户端可能允许同时跟a.example.com和b.example.com这两个地址建立连接,但是如果同时和主机建立三十个连接,这可能是不允许的)。例如:在Web浏览器环境下,客户端需要考虑在用户打开的多个tab页中设置一个同时建立连接的数目限制。

注意:这个限制使得脚本仅仅通过创建大量的WebSocket连接来进行拒绝服务攻击变得更难了。服务端可以在关闭连接前就停止攻击,从而进一步减小负载,这样会减少客户端的重连率。

注意:客户端可以与单个主机建立的WebSocket连接数量是没有限制的。当建立的连接过多时,服务端可以拒绝和主机/IP地址建立的连接,同时服务端在负载过高时也可以主动断开占用资源的连接。

3.使用代理:如果客户端在使用WebSocket协议来连接特定的主机和端口时使用了配置的代理,那么客户端应该连接到那个代理并且通过这个代理去和指定的主机和端口建立一个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连接。

注意:如果可以,实现不暴露明显界面的来给WebSocket选择与其他代理分开的代理推荐使用SOCKS5(RFC1928)代理供WebSocket连接,如果不行的话,使用配置了HTTPS连接的代理优于使用HTTP连接的代理。

为了自动配置脚本,传递参数的URI必须包含定义在第三节WebSocket URI中的主机(host)、端口(port)、资源名称(resource name)和安全(secure)字段。

注意:WebSocket协议可以根据定义的规范配置到代理自动配置脚本("ws"代表非加密连接,"wss"代表加密连接)。

4.如果连接没有被打开,或者由于直连失败或者代理返回了一个错误,那么客户端必须断开WebSocket连接,并且停止重试连接。

5.如果安全(secure)字段存在,客户端必须在连接建立以后、发送握手数据之前进行TLS握手。如果TLS握手失败(比如服务端正数没有验证通过),那么客户端必须断开WebSocket连接。否则,所有后续的在此频道上面的数据通信都必须在加密的通道中传输。

客户端在TLS握手时必须使用服务器名称指示扩展(SNI,Server Name Indication)。

一旦到服务端的连接被建立了(包括通过一个代理或者通过一个TLS加密通道),客户端必须发送一个开始握手的数据包给服务端。这个数据包由一个HTTP升级请求构成,包含一系列必须的和可选的header字段。握手的具体要求如下所示:

1.握手必须是一个在RFC2616指定的有效的HTTP请求。

2.这个请求方法必须是GET,而且HTTP的版本至少需要1.1。
例如:如果WebSocket的URI是"ws://example.com/chat",那么发送的请求头第一行就应该是"GET /chat HTTP/1.1"。

3.请求的"Request-URI"部分必须与第三章中定义的资源名称(resource name)匹配,或者必须是一个http/https绝对路径的URI,当解析URI时,有一个资源名称(resource name)、主机(host)和端口(port)与相对应的ws/wss匹配。

4.请求必须包含一个Host header字段,它包含了一个主机(host)字段加上一个紧跟在":"之后的端口(port)字段(如果端口不存在则使用默认端口)。

5.这个请求必须包含一个Upgrade header字段,它的值必须包含"websocket"。

6.请求必须包含一个Connectionheader字段,它的值必须包含"Upgrade"。

7.请求必须包含一个名为Sec-WebSocket-Key的header字段。这个header字段的值必须是由一个随机生成的16字节的随机数通过base64(见RFC4648的第四章)编码得到的。每一个连接都必须随机的选择随机数。

注意:例如,如果随机选择的值的字节顺序为0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,那么header字段的值就应该是"AQIDBAUGBwgJCgsMDQ4PEC=="。

8.如果这个请求来自一个浏览器,那么请求必须包含一个Originheader字段。如果请求是来自一个非浏览器客户端,那么当该客户端这个字段的语义能够与示例中的匹配时,这个请求也可能包含这个字段。这个header字段的值为建立连接的源代码的源地址ASCII序列化后的结果。通过RFC6454可以知道如何构造这个值。例如,如果在www.example.com域下面的代码尝试与ww2.example.com这个地址建立连接,那么这个header字段的值就应该是"www.example.com"。

9.这个请求必须包含一个名为Sec-WebSocket-Version的字段。这个header字段的值必须为13。
注意:尽管这个文档草案的版本(09,10,11和12)都已经发布(这些协议大部分是编辑上的修改和澄清,而不是对无线协议的修改),9,10,11,12这四个值不被认为是有效的Sec-WebSocket-Version的值。这些值被IANA保留,但是没有被用到过,以后也不会被使用。

10.这个请求可能会包含一个名为Sec-WebSocket-Protocol的header字段。如果存在这个字段,那么这个值包含了一个或者多个客户端希望使用的用逗号分隔的根据权重排序的子协议。这些子协议的值必须是一个非空字符串,字符的范围是U+0021到U+007E,但是不包含其中的定义在RFC2616中的分隔符,并且每个协议必须是一个唯一的字符串。ABNF的这个header字段的值是在RFC2616定义了构造方法和规则的1#token。

11.这个请求可能包含一个名为Sec-WebSocket-Extensions字段。如果存在这个字段,这个值表示客户端期望使用的协议级别的扩展。这个header字段的具体内容和格式具体见9.1节。

12.这个请求可能还会包含其他的文档中定义的header字段,如cookie(RFC6265)或者认证相关的header字段如Authorization字段(RFC2616)。

一旦客户端的握手请求发送出去,那么客户端必须在发送后续数据前等待服务端的响应。客户端必须通过以下的规则验证服务端的请求:

1.如果客户端收到的服务端返回状态码不是101,客户端需要处理每个HTTP请求的响应。特别的是,客户端需要在收到401状态码的时候可能需要进行验证;服务端可能会通过3xx的状态码来将客户端进行重定向(但是客户端不要求遵守这些)等。否则,遵循下面的步骤。

3.如果客户端收到的响应缺少一个Upgrade header字段或者Upgrade header字段包含一个不是"websocket"的值(该值不区分大小写),那么客户端必须关闭连接。

3.如果客户端收到的响应缺少一个Connection header字段或者Connection header字段不包含"Upgrade"的值(该值不区分大小写),那么客户端必须关闭连接。

4.如果客户端收到的Sec-WebSocket-Accept header字段或者Sec-WebSocket-Accept header字段不等于通过Sec-WebSocket-Key字段的值(作为一个字符串,而不是base64解码后)和"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"串联起来,忽略所有前后空格进行base64 SHA-1编码的值,那么客户端必须关闭连接。

5.如果客户端收到的响应包含一个Sec-WebSocket-Extensions header字段,并且这个字段使用的extension值在客户端的握手请求里面不存在(即服务端使用了一个客户端请求中不存在的值),那么客户端必须关闭连接。(解析这个header字段来确定使用哪个扩展在9.1节中有讨论。)

6.如果客户端收到的响应包含一个Sec-WebSocket-Protocol header字段,并且这个字段包含了一个没有在客户端握手中出现的子协议(即服务端使用了一个客户端请求中子协议字段不存在的值),那么客户端必须关闭连接。

如果客服务端的响应没有符合定义在这一节和4.2.2节中的服务端握手响应定义的要求,那么客户端也会断开连接。

请注意,根据RFC2616,所有的header字段名称在HTTP请求和HTTP请求响应中都是不区分大小写的。

如果服务端的响应通过了上述的验证过程,那么WebSocket就已经建立连接了,并且WebSocket的连接状态也到了OPEN状态。使用的扩展被定义为一个字符串(可能为空),它是在服务端响应握手时候提供的Sec-WebSocket-Extensions字段的值,如果这个header字段在握手响应中不存在,那么就是一个空值。使用的子协议值是在服务端响应握手中提供的Sec-WebSocket-protocol字段的值,如果服务端响应握手时没有这个header字段,那么这个值也为空。另外,如过服务端握手响应是审核制了任何cookie的header字段(定义在RFC6265),这些cookie被称为在服务端响应握手时设置的cookie(Cookies Set During the Server's Opening Handshake)。

4.2 服务端要求

服务端可以将连接的管理挂载到其他的网络代理赏,如负载均衡器或者反向代理。在这种情况下,这篇规范对于服务端的目标是包含从第一个设备从建立到断开连接的TCP连接周期到服务端接受请求,发送响应的所有服务测的基础设施部分。

示例:一个数据中心可能有一个响应WebSocket握手请求的服务器,但是它将收到的数据帧都通过连接传递给另一个服务器来处理。在本文档中,"服务端(server)"包含这两者。

4.2.1 解析客户端的握手协议

当客户端开始一个WebSocket连接时,他会发送一个开始握手协议。为了获得必要的信息来保证服务端的握手响应,服务端必须解析这个客户端这部分的握手协议。

客户端的握手协议包含以下几部分。当服务的收到一个握手请求,发现客户端并没有发送一个符合以下内容的握手协议(注意在RFC2616中的每一项,header字段的顺序是不重要的),包括但不限于在握手协议中有不合法的ANBF语法,服务端必须立即停止处理客户端的握手请求并且在响应中返回一个表示错误的HTTP错误码(如400 Bad Request)。

1.一个HTTP/1.1或者跟高版本的GET请求,包含一个在第三章定义的应该被解析为资源名称(resource name)"Request-URI"字段(或者包含资源名称(resource name)的HTTP/HTTPS绝对路径)。

2.包含服务端权限的Host header字段。

3.不区分大小写的值为"websocket"的Upgrade header字段。

4.不区分大小写的值为"Upgrade"的Connection header字段。

5.值为base64编码(见RFC4648的第四章)后长度为16字节的Sec-WebSocket-Key header字段。

6.值为13的Sec-WebSocket-Version header值。

7.可选的Origin header字段。所有的浏览器都会发送这个字段。缺少此字段的连接不应该认为是来自浏览器。

8.可选的Sec-WebSocket-Protocolheader字段,对应的值为客户端支持的子协议,根据权重进行排序。

9.可选的Sec-WebSocket-Extensionsheader字段,对应的值为客户端可以使用的扩展。这个字段具体内容会在第9.1节再进行讨论。

10.可选的其他字段,如使用cookie或者服务器请求认证的字段。不识别的header字段会依据RFC2616中内容被忽略。

4.2.2 发送服务端握手响应请求

当客户端和服务端建立了一个WebSocket连接,服务端也必须完成接受连接的下面说明的步骤,并且发送一个服务端握手响应。

1.如果是一条建立在HTTPS(HTTPS+TLS)端口的连接,通过这个链接完成TLS握手过程。如果这次握手失败(例如,客户端在"server_name"扩展中制定了主机名,但是服务端没有这个主机),那么关闭这条连接;否则,后续这个连接的所有的数据传递(包括服务端握手响应)都必须使用一个加密的通道。

2.服务端可以选择而外面的客户端认证,例如,通过返回401状态码和在RFC2616说明的相对应的WWW-Authenticate header字段。

3.服务端可能通过使用3xx的状态码(见RFC2616)来重定向客户端。注意这个步骤可以发生在上面说到的认证之前、之后或者和认证一起。

4.构造以下信息:
源(origin)
Originheader字段在客户端的握手请求中表示建立连接的脚本属于哪一个源。这个源信息被序列化为ASCII,并且转换为小写。服务端可以使用这个信息来作为判断是否接受这个链接的部分参考内容。如果服务端没有过滤源,那么他会接受任意源的连接。如果服务端没有接受这个连接,那么它必须返回一个对应的HTTP错误码(如403 Forbidden)并且终端这一节描述的WebSocket握手过程。更多详情可以阅读第十章。

关键值(key)
Sec-WebSocket-Keyheader字段在客户端的握手请求中表示一个长度为16字节的base64编码的值。这个编码后的值是用于服务端握手的创建过程,用来表示接受了这个连接。服务端没有必要对Sec-WebSocket-Key值进行解码。

版本(version)
Sec-WebSocket-Versionheader字段在客户端握手请求中表示了客户端建立连接使用的WebSocket协议版本。如果这个版本和服务端的版本没有匹配上,那么服务端必须中断本章说的WebSocket连接,并且发送一个对应的HTTP错误码(例如426 Upgrade Required),同时返回一个Sec-WebSocket-Versionheader字段用来标识服务端能够识别的版本号。

资源名称(resource name)
服务端提供的服务标识符。如果这个服务端提供多种服务,那么这个值应该是来自客户端握手请求中的GET方法中的"Request-URI"字段。如果请求的服务支持,那么服务端必须发送一个相对应的HTTP错误码(例如404 Not Found)并且终端WebSocket连接。

子协议(subprotocol)
服务端准备使用的代表子协议的单个值或者为空。这个值必须选择客户端握手协议中由Sec-WebSocket-Protocol字段中提供的值,服务端会在这个连接中使用此值(任意)。如果客户端握手协议中没有包含这个字段或者服务端不支持客户端请求中提供的任意一个子协议,那么这个值只能为空。没有此header值就表明该值为空(这意味着服务端可以不选择客户端传递的任意一个子协议,禁止在响应请求中添加一个Sec-WebSocket-Protocol字段)。空字符串与空值不同,并且空值对于此字段来说是一个不合法值。ABNF对于整个字段的定义和构造规则可以见RFC2616。

扩展(extensions)
表示一个服务端准备使用的协议级扩展列表(可能为空)。如果服务端支持多种扩展,那么这个值必须是客户端握手中已有的数值,是从Sec-WebSocket-Extensions字段中取一到多个值。该字段不存在时则表示此值为空。空字符串与空值不同。客户端没有列举的扩展静止被
使用。应该选择哪些值和如何进行解析可以见9.1节。

5.如果服务端选择接受一条连接,他必须发送一个如下说明的有效的HTTP请求来进行相应。

1.像RFC2616中说明的一样,状态码为101的状态行。比如看上去像这种的:"HTTP/1.1 101 Switching Protocols"。

2.像RFC2616中说明的一样,值为"websocket"的Upgrade header字段。

3.值为"Upgrade"的Connection header字段。

4.一个Sec-WebSocket-Accept header字段。这个值由第4.2.2节的第4步提到的key来进行构造,通过和字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"拼接在一起进行SHA-1哈希运算,得到一个20字节的值,然后对这20字节进行base64编码。

ABNF对这个字段定义如下:
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-Keyheader字段的值为"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. 可选的Sec-WebSocket-Protocol字段,值为定义在第4.2.2节第4点中的子协议中。

6. 可选的Sec-WebSocket-Extensions字段,值为定义在4.2.2节第4点中的扩展中。

这样服务端握手响应就完成了。如果服务端完成了上述步骤时也没有关闭中断WebSocket连接,那么服务端回考虑建立这个WebSocket链接并且将WebSocket连接状态置为OPEN。在此刻,服务端就可以开始发送(和接收)数据了。

4.3 收集握手中使用的新的ABNF的header字段

这一节使用在RFC2616第2.1节定义的ABNF语法和规则,包括隐含的*LWS规则(implied *LWS rule)。

请注意本节中使用了一下ABNF规定。一些规则名称对应一些header字段。这样的规则表示对应的header字段的值,例如Sec-WebSocket-Key的ABNF描述了Sec-WebSocket-Keyheader字段的值的语法。在名字中带有"-Client"后缀的ABNF规则只适用于客户端发送给服务端的请求;而名字中带有"-Server"后缀的ABNF规则则只适用于服务端给客户端发送的请求响应。例如ABNF规则Sec-WebSocket-Protocol-Client表示客户端发送给服务端的请求中的Sec-WebSocket-Protocol字段的值。
以下的新的header字段可以在客户端向服务端发送握手请求时使用:
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) ]
    ;当使用带引号的字符串语法变体时,在引号转义后面的值必须和ABNF"标记(token)"一致。
NZDIGIT =  "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
version = DIGIT | (NZDIGIT DIGIT) | ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)
    ; 范围是从0-255,没有前导0

以下的新的header字段可以在服务端向客户端发送握手响应请求时使用:
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-Versionheader字段),客户端可以在最初请求时选择WebSocket协议的版本号(客户端不必要支持最新的版本)。如果服务端支持请求的版本并且我收到消息是有效的,那么服务端会接受这个版本。如果服务端不支持客户端请求的版本,那么服务端必须返回一个Sec-WebSocket-Versionheader字段(或者多个Sec-WebSocket-Versionheader字段)包含服务端支持的所有版本。在这种情况下,如果客户端支持其中任意一个版本,它可以选择一个新的版本值重新发起握手请求。

下面的示例演示了如何进行上面所述的版本协商:
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



第五章--数据帧(Data Framing)

概述

本文为WebSocket协议的第五章,本章翻译的主要内容为WebSocket传输的数据相关内容。

5.1 概览

在WebSocket协议中,数据是通过一系列数据帧来进行传输的。为了避免由于网络中介(例如一些拦截代理)或者一些在第10.3节讨论的安全原因,客户端必须在它发送到服务器的所有帧中添加掩码(Mask)(具体细节见5.3节)。(注意:无论WebSocket协议是否使用了TLS,帧都需要添加掩码)。服务端收到没有添加掩码的数据帧以后,必须立即关闭连接。在这种情况下,服务端可以发送一个在7.4.1节定义的状态码为1002(协议错误)的关闭帧。服务端禁止在发送数据帧给客户端时添加掩码。客户端如果收到了一个添加了掩码的帧,必须立即关闭连接。在这种情况下,它可以使用第7.4.1节定义的1002(协议错误)状态码。(这些规则可能会在将来的规范中放开)。

基础的数据帧协议使用操作码、有效负载长度和在“有效负载数据”中定义的放置“扩展数据”与“引用数据”的指定位置来定义帧类型。特定的bit位和操作码为将来的协议扩展做了保留。一个数据帧可以在开始握手完成之后和终端发送了一个关闭帧之前的任意一个时间通过客户端或者服务端进行传输(第5.5.1节)。

5.2 基础帧协议
在这节中的这种数据传输部分的有线格式是通过ABNFRFC5234来进行详细说明的。(注意:不像这篇文档中的其他章节内容,在这节中的ABNF是对bit组进行操作。每一个bit组的长度是在评论中展示的。在线上编码时,最高位的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
​    必须设置为0,除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义,接收终端必须断开WebSocket连接。
Opcode: 4 bit
​    定义“有效负载数据”的解释。如果收到一个未知的操作码,接收终端必须断开WebSocket连接。下面的值是被定义过的。
​    %x0 表示一个持续帧
​    %x1 表示一个文本帧
​    %x2 表示一个二进制帧
​    %x3-7 预留给以后的非控制帧
​    %x8 表示一个连接关闭包
​    %x9 表示一个ping包
​    %xA 表示一个pong包
​    %xB-F 预留给以后的控制帧
Mask: 1 bit

mask标志位,定义“有效负载数据”是否添加掩码。如果设置为1,那么掩码的键值存在于Masking-Key中,根据5.3节描述,这个一般用于解码“有效负载数据”。所有的从客户端发送到服务端的帧都需要设置这个bit位为1。

Payload length: 7 bits, 7+16 bits, or 7+64 bits
​    以字节为单位的“有效负载数据”长度,如果值为0-125,那么就表示负载数据的长度。如果是126,那么接下来的2个bytes解释为16bit的无符号整形作为负载数据的长度。如果是127,那么接下来的8个bytes解释为一个64bit的无符号整形(最高位的bit必须为0)作为负载数据的长度。多字节长度量以网络字节顺序表示(译注:应该是指大端序和小端序)。在所有的示例中,长度值必须使用最小字节数来进行编码,例如:长度为124字节的字符串不可用使用序列126,0,124进行编码。有效负载长度是指“扩展数据”+“应用数据”的长度。“扩展数据”的长度可能为0,那么有效负载长度就是“应用数据”的长度。

Masking-Key: 0 or 4 bytes
所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。如果mask标志位(1 bit)为1,那么这个字段存在,如果标志位为0,那么这个字段不存在。在5.3节中会介绍更多关于客户端到服务端增加掩码的信息。
Payload data: (x+y) bytes
​    “有效负载数据”是指“扩展数据”和“应用数据”。
Extension data: x bytes
​    除非协商过扩展,否则“扩展数据”长度为0 bytes。在握手协议中,任何扩展都必须指定“扩展数据”的长度,这个长度如何进行计算,以及这个扩展如何使用。如果存在扩展,那么这个“扩展数据”包含在总的有效负载长度中。
Application data: y bytes
​    任意的“应用数据”,占用“扩展数据”后面的剩余所有字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。
基础数据帧协议通过ABNF进行了正式的定义。需要重点知道的是,这些数据都是二进制的,而不是ASCII字符。例如,长度为1 bit的字段的值为%x0 / %x1代表的是一个值为0/1的单独的bit,而不是一整个字节(8 bit)来代表ASCII编码的字符“0”和“1”。一个长度为4 bit的范围是%x0-F的字段值代表的是4个bit,而不是字节(8 bit)对应的ASCII码的值。不要指定字符编码:“规则解析为一组最终的值,有时候是字符。在ABNF中,字符仅仅是一个非负的数字。在特定的上下文中,会根据特定的值的映射(编码)编码集(例如ASCII)”。在这里,指定的编码类型是将每个字段编码为特定的bits数组的二进制编码的最终数据。
ws-frame =

frame-fin; 长度为1 bit
frame-rsv1; 长度为1 bit
frame-rsv2; 长度为1 bit
frame-rsv3; 长度为1 bit
frame-opcode; 长度为4 bit
frame-masked; 长度为1 bit
frame-payload-length; 长度为7或者7+16或者7+64 bit
[frame-masking-key]; 长度为32 bit
frame-payload-data; 长度为大于0的n*8 bit(其中n>0)

frame-fin =
%x0,除了以下为1的情况
%x1,最后一个消息帧
长度为1 bit

frame-rsv1 =
%x0 / %x1,长度为1 bit,如果没有协商则必须为0

frame-rsv2 =
%x0 / %x1,长度为1 bit,如果没有协商则必须为0

frame-rsv3 =
%x0 / %x1,长度为1 bit,如果没有协商则必须为0

frame-opcode =
frame-opcode-non-control
frame-opcode-control
frame-opcode-cont

frame-opcode-non-control
%x1,文本帧
%x2,二进制帧
%x3-7,保留给将来的非控制帧
长度为4 bit

frame-opcode-control
%x8,连接关闭
%x9,ping帧
%xA,pong帧
%xB-F,保留给将来的控制帧
长度为4 bit

frame-masked
%x0,不添加掩码,没有frame-masking-key
%x1,添加掩码,存在frame-masking-key
长度为1 bit

frame-payload-length
%x00-7D,长度为7 bit
%x7E frame-payload-length-16,长度为7+16 bit
%x7F frame-payload-length-63,长度为7+64 bit

frame-payload-length-16
%x0000-FFFF,长度为16 bit

frame-payload-length-63
%x0000000000000000-7FFFFFFFFFFFFFFF,长度为64 bit

frame-masking-key
4(%x00-FF),当frame-mask为1时存在,长度为32 bit

frame-payload-data
frame-masked-extension-data frame-masked-application-data,当frame-masked为1时
frame-unmasked-extension-data frame-unmasked-application-data,当frame-masked为0时

frame-masked-extension-data
*(%x00-FF),保留给将来的扩展,长度为n*8,其中n>0

frame-masked-application-data
*(%x00-FF),长度为n*8,其中n>0

frame-unmasked-extension-data
*(%x00-FF),保留给将来的扩展,长度为n*8,其中n>0

frame-unmasked-application-data
*(%x00-FF),长度为n*8,其中n>0

5.3 客户端到服务端添加掩码

添加掩码的数据帧必须像5.2节定义的一样,设置frame-masked字段为1。

掩码值像第5.2节说到的完全包含在帧中的frame-masking-key上。它是用于对定义在同一节中定义的帧负载数据Payload data字段中的包含Extension data和Application data的数据进行添加掩码。

掩码字段是一个由客户端随机选择的32bit的值。当准备掩码帧时,客户端必须从允许的32bit值中须知你咋一个新的掩码值。掩码值必须是不可被预测的;因此,掩码必须来自强大的熵源(entropy),并且给定的掩码不能让服务器或者代理能够很容易的预测到后续帧。掩码的不可预测性对于预防恶意应用作者在网上暴露相关的字节数据至关重要。RFC 4086讨论了安全敏感的应用需要一个什么样的合适的强大的熵源。

掩码不影响Payload data的长度。进行掩码的数据转换为非掩码数据,或者反过来,根据下面的算法即可。这个同样的算法适用于任意操作方向的转换,例如:对数据进行掩码操作和对数据进行反掩码操作所涉及的步骤是相同的。

表示转换后数据的八位字节的i(transformed-octet-i)是表示的原始数据的i(original-octet-i)与索引i模4得到的掩码值(masking-key-octet-j)经过异或操作(XOR)得到的:
j = i MOD 4
transfromed-octed-i = original-octet-i XOR masking-key-octet-j

在规范中定义的位于frame-payload-length字段的有效负载的长度,不包括掩码值的长度。它只是Payload data的长度。如跟在掩码值后面的字节数组的数。

5.4 消息分片

消息分片的主要目的是允许发送一个未知长度且消息开始发送后不需要缓存的消息。如果消息不能被分片,那么一端必须在缓存整个消息,因此这个消息的长度必须在第一个字节发送前就需要计算出来。如果有消息分片,服务端或者代理可以选择一个合理的缓存长度,当缓存区满了以后,就想网络发送一个片段。

第二个消息分片使用的场景是不适合在一个逻辑通道内传输一个大的消息占满整个输出频道的多路复用场景。多路复用需要能够将消息进行自由的切割成更小的片段来共享输出频道。(注意:多路复用的扩展不在这个文档中讨论)。

除非在扩展中另有规定,否则帧没有语义的含义。如果客户端和服务的没有协商扩展字段,或者服务端和客户端协商了一些扩展字段,并且代理能够完全识别所有的协商扩展字段,在这些扩展字段存在的情况下知道如何进行帧的合并和拆分,代理就可能会合并或者拆分帧。这个的一个含义是指在缺少扩展字段的情况下,发送者和接收者都不能依赖特定的帧边界的存在。

消息分片相关的规则如下:

一个未分片的消息包含一个设置了FIN字段(标记为1)的单独的帧和一个除0以外的操作码。

一个分片的消息包含一个未设置的FIN字段(标记为0)的单独的帧和一个除0以外的操作码,然后跟着0个或者多个未设置FIN字段的帧和操作码为0的帧,然后以一个设置了FIN字段以及操作码为0的帧结束。一个分片的消息内容按帧顺序组合后的payload字段,是等价于一个单独的更大的消息payload字段中包含的值;然而,如果扩展字段存在,因为扩展字段定义了Extension data的解析方式,因此前面的结论可能不成立。例如:Extension data可能只出现在第一个片段的开头,并适用于接下来的片段,或者可能每一个片段都有Extension data,但是只适用于特定的片段。在Extension data不存在时,下面的示例演示了消息分片是如何运作的。示例:一个文本需要分成三个片段进行发送,第一个片段包含的操作码为0x1并且未设置FIN字段,第二个片段的操作码为0x0并且未设置FIN字段,第三个片段的操作码为0x0并且设置了FIN字段。

控制帧(见5.5节)可能被插入到分片消息的中间。控制帧不能被分片。

消息片段必须在发送端按照顺序发送给接收端。

除非在扩展中定义了这种嵌套的逻辑,否则一条消息分的片不能与另一条消息分的片嵌套传输。

终端必须有能力来处理在分片的消息中的控制帧。

发送端可能会创建任意大小的非控制消息片段。

客户端和服务端必须同时支持分片和不分片消息。

控制帧不能被分片,并且代理不允许改变控制帧的片段。

如果有保留字段被使用并且代理不能理解这些字段的值时,那么代理不能改变消息的片段。

在扩展字段已经被协商过,但是代理不知道协商扩展字段的具体语义时,代理不能改变任意消息的片段。同样的,扩展不能看到WebSocket握手(并且得不到通知内容)导致WebSocket的连接禁止改变连接过程中任意的消息片段。

作为这些规则的结论,所有的消息片段都是同类型的,并且设置了第一个片段的操作码(opccode)字段。控制帧不能被分片,所有的消息分片类型必须是文本或者二进制,或者是保留的任意一个操作码。

注:如果控制帧没有被打断,心跳(ping)的等待时间可能会变很长,例如在一个很大的消息之后。因此,在分片的消息传输中插入控制帧是有必要的。

实践说明:如果扩展字段不存在,接收者不需要使用缓存来存储下整个消息片段来进行处理。例如:如果使用一个流式API,再收到部分帧的时候就可以将数据交给上层应用。然而,这个假设对以后所有的WebSocket扩展可能不一定成立。

5.5 控制帧

控制帧是通过操作码最高位的值为1来进行区分的。当前已经定义的控制帧操作码包括0x8(关闭),0x9(心跳Ping)和0xA(心跳Pong)。操作码0xB-0xF没有被定义,当前被保留下来做为以后的控制帧。

控制帧是用于WebSocket的通信状态的。控制帧可以被插入到消息片段中进行传输。

所有的控制帧必须有一个126字节或者更小的负载长度,并且不能被分片。

5.5.1 关闭(Close)

控制帧的操作码值是0x8。

关闭帧可能包含内容(body)(帧的“应用数据”部分)来表明连接关闭的原因,例如终端的断开,或者是终端收到了一个太大的帧,或者是终端收到了一个不符合预期的格式的内容。如果这个内容存在,内容的前两个字节必须是一个无符号整型(按照网络字节序)来代表在7.4节中定义的状态码。跟在这两个整型字节之后的可以是UTF-8编码的的数据值(原因),数据值的定义不在此文档中。数据值不一定是要人可以读懂的,但是必须对于调试有帮助,或者能传递有关于当前打开的这条连接有关联的信息。数据值不保证人一定可以读懂,所以不能把这些展示给终端用户。

从客户端发送给服务端的控制帧必须添加掩码,具体见5.3节。

应用禁止在发送了关闭的控制帧后再发送任何的数据帧。

如果终端收到了一个关闭的控制帧并且没有在以前发送一个关闭帧,那么终端必须发送一个关闭帧作为回应。(当发送一个关闭帧作为回应时,终端通常会输出它收到的状态码)响应的关闭帧应该尽快发送。终端可能会推迟发送关闭帧直到当前的消息都已经发送完成(例如:如果大多数分片的消息已经发送了,终端可能会在发送关闭帧之前将剩余的消息片段发送出去)。然而,已经发送关闭帧的终端不能保证会继续处理收到的消息。

在已经发送和收到了关闭帧后,终端认为WebSocket连接以及关闭了,并且必须关闭底层的TCP连接。服务端必须马上关闭底层的TCP连接,客户端应该等待服务端关闭连接,但是也可以在收到关闭帧以后任意时间关闭连接。例如:如果在合理的时间段内没有收到TCP关闭指令。

如果客户端和服务端咋同一个时间发送了关闭帧,两个终端都会发送和接收到一条关闭的消息,并且应该认为WebSocket连接已经关闭,同时关闭底层的TCP连接。

5.5.2 心跳Ping

心跳Ping帧包含的操作码是0x9。

关闭帧可能包含“应用数据”。

如果收到了一个心跳Ping帧,那么终端必须发送一个心跳Pong 帧作为回应,除非已经收到了一个关闭帧。终端应该尽快恢复Pong帧。Pong帧将会在5.5.3节讨论。

终端可能会在建立连接后与连接关闭前中间的任意时间发送Ping帧。

注意:Ping帧可能是用于保活或者用来验证远端是否仍然有应答。

5.5.3 心跳Pong

心跳Ping帧包含的操作码是0xA。

5.5.2节详细说明了Ping帧和Pong帧的要求。

作为回应发送的Pong帧必须完整携带Ping帧中传递过来的“应用数据”字段。

如果终端收到一个Ping帧但是没有发送Pong帧来回应之前的pong帧,那么终端可能选择用Pong帧来回复最近处理的那个Ping帧。

Pong帧可以被主动发送。这会作为一个单项的心跳。预期外的Pong包的响应没有规定。

5.6 数据帧

数据帧(例如非控制帧)的定义是操作码的最高位值为0。当前定义的数据帧操作吗包含0x1(文本)、0x2(二进制)。操作码0x3-0x7是被保留作为非控制帧的操作码。数据帧会携带应用层/扩展层数据。操作码决定了携带的数据解析方式:

文本
“负载字段”是用UTF-8编码的文本数据。注意特殊的文本帧可能包含部分UTF-8序列;然而,整个消息必须是有效的UTF-8编码数据。重新组合消息后无效的UTF-8编码数据处理见8.1节。

二进制
“负载字段”是任意的二进制数据,二进制数据的解析仅仅依靠应用层。

5.7 示例

一个单帧未添加掩码的文本消息
0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (内容为"Hello")

一个单帧添加掩码的文本消息
0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (内容为Hello")

一个分片的未添加掩码的文本消息
0x01 0x03 0x48 0x65 0x6c (内容为"Hel")
0x80 0x02 0x6c 0x6f (内容为”lo")

未添加掩码的Ping请求和添加掩码的Ping响应(译者注:即Pong)
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含内容为”Hello", 但是文本内容是任意的)
0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (包含内容为”Hello", 匹配ping的内容)

256字节的二进制数据放入一个未添加掩码数据帧
0x82 0x7E 0x0100 [256 bytes of binary data\]

64KB二进制数据在一个非掩码帧中
0x82 0x7F 0x0000000000010000 [65536 bytes of binary data\]

5.8 扩展性

这个协议的设计初衷是允许扩展的,可以在基础协议上增加能力。终端的连接必须在握手的过程中协商使用的所有扩展。在规范中提供了从0x3-0x7和0xB-0xF的操作码,在数据帧Header中的“扩展数据”字段、frame-rsv1、frame-rsv2、frame-rsv3字段都可以用于扩展。扩展的协商讨论将在以后的9.1节中详细讨论。下面是一些符合预期的扩展用法。下面的列表不完整,也不是规范中内容。

“扩展数据”可以放置在“负载数据“中的应用数据”之前的位置。
保留的字段可以在每一帧需要时被使用。
保留的操作码的值可以被定义。
如果需要更多的操作码,那么保留的操作码字段可以被定义。
保留的字段或者“扩展”操作码可以在“负载数据”之中的分配额外的位置来定义,这样可以定义更大的操作码或者更多的每一帧的字段。


第六章--发送与接收消息(Sending and Receiving Data)

概述

本文为 WebSocket 协议的第六章,本文翻译的主要内容为 WebSocket 消息发送与接收相关内容。

发送与接收消息

6.1 发送数据

为了通过 WebSocket 连接发送一条 WebSocket 消息,终端必须遵循以下几个步骤:

终端必须保证 WebSocket 连接处于 OPEN 状态(见第 4.1 节和第 4.2.2 节)。如果 WebSocket 连接的任意一端的状态发生了改变,终端必须中止以下步骤。

终端必须将数据按照第 5.2 节定义的 WebSocket 帧进行封装。如果需要发送的数据过大或者在终端希望开始发消息时,如果数据在整体性这一点上不可用,那么终端可能会选择通过在第 5.4 节中定义的一系列帧来进行封装。

包含数据的第一帧操作码(帧操作码)必须根据第 5.2 节中的内容设置的合适的值,以便接收者将数据解析为文本或者二进制数据。

最后一个包含数据的帧的 FIN ( FIN 帧)字段必须和第 5.2 节中定义的一样设置为 1 。

如果数据被发送到了客户端,数据帧必须和第 5.3 节中定义的一样添加掩码。

如果在 WebsSocket 连接中有协商扩展(第 9 章),在这些扩展中的定义和注意事项也许要额外考虑。

被格式化的帧必须通过底层的网络连接进行传输。

6.2 接收数据

为了接收 WebSocket 数据,终端需要监听底层网络连接。输入的数据必须通过第 5.2 节定义的 WebSocket 帧进行解析。如果收到了一个控制帧(第 5.5 节),那么这个帧必须如 5.5 节中定义的方式进行处理。如果收到的是一个数据帧,那么终端必须注意 5.2 节中的定义在操作码(帧操作码)中的数据类型。在这一帧中的“应用数据”被定义为消息的数据。如果帧中包含未分片的数据(第 5.4 节),那么就认为:一条 WebSocket 消息的数据和类型被收到了。如果帧是分片数据的一部分,那么随后的帧包含的“应用数据”连起来就是数据的格式。当通过 FIN 字段(FIN帧)表示的最后一个片段被收到时,我们可以说:一条 WebSocket 消息的数据(由片段组装起来的“应用数据”数据组成)和类型(注意分片消息的第一帧)已经被收到了。接下来的数据帧必须是属于一条新的 WebSocket 消息。

扩展(第 9 章)可能改变数据如何理解的方式,具体包括消息的内容边界。扩展,除了在“应用数据”之前添加“扩展数据”之外,也可以修改“应用数据”(例如压缩它)。像第 5.3 节中说的那样,服务端在收到客户端的数据帧时必须去除掩码。


第七章--关闭连接(Closing the Connection)

概述

本文为 WebSocket 协议的第七章,本文翻译的主要内容为 WebSocket 连接关闭相关内容。

关闭连接

7.1 定义

7.1.1 关闭 WebSocket 连接

要关闭 WebSocket 连接,终端需要关闭底层的 TCP 连接。终端需要使用一个方法来干净的关闭TCP连接,还有 TLS 会话,如果可能的话,抛弃后面可能受到的任意字符。终端可能会在需要的时候,通过任何方式来关闭连接,例如在收到攻击时。

在底层的 TCP 连接中,通常大多数情况下,服务端应该先关闭,所以是服务端而不是客户端保持 TIME_WAIT 状态(因为客户端先关闭的话,这会阻止服务端在2 MSL 内重新打开这条连接,而如果服务器处于 TIME_WAIT 状态下,如果收到了一个带有更大序列号的新的 SYN 包时,也能够立即响应重新打开连接,从而不会对服务器产生影响)。反常情况(例如在合理的时间后,服务端收到一个 TCP 关闭包)下,客户端应该开始关闭 TCP 连接。像这样的,当服务端进入关闭 WebSocket 连接状态时,它应该立刻准备关闭 TCP 连接,然后当客户端客户端准备关闭连接时,他应该等待服务端的 TCP 关闭包。

用 C 语言的 Berkeley socket 作为例子来展示如何彻底的关闭连接,一端需要用 SHUP_WR 调用 shutdown() 方法,调用 recv() 直到获得一个值为 0 的表示对面也准备有序关闭连接的返回值,然后最后调用 close() 来关闭 socket 通道。

7.1.2 开始进行 WebSocket 关闭握手

用一个状态码 code (第 7.4 节)和一个可选的关闭原因 reason (第 7.1.6 节)来开始 WebSocket 关闭握手,终端必须发送一个在第 5.5.1 节中描述的一样的关闭帧,将状态码设置为 code 字段,将关闭原因设置为 reaons 字段。一旦终端已经发送和收到了关闭控制帧,那么终端应该像第 7.1.1 节中定义的一样关闭 WebSocket 连接。

7.1.3 已经开始 WebSocket 关闭握手

在发送或者收到了关闭帧时,我们可以说已经开始 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 节中定义的一样,关闭帧可以包含一个关闭的状态码和指定的原因。WebSocket 连接的关闭可能是同时由另一个终端发起。WebSocket 关闭状态码是在第 7.4 节中定义的在第一关闭帧中的由实现该协议的应用程序接收的状态码。如果关闭帧中没有包含状态码,WebSocket 关闭状态码被默认为1005。如果WebSocket 已经关闭并且终端没有收到任何的关闭帧(例如发生了可能底层的传输连接突然丢失的情况),那么WebSocket 关闭状态码被默认为1006。

注:两个终端可能没有就WebSocket 关闭状态码的值达成一致。例如:如果远端发送一个关闭帧,但是本地应用没有从它的 socket 缓冲区中读到关闭帧的数据,同时本地应用单独的决定关闭连接并且发送了一个关闭帧,那么两个终端都发送了并且会收到一个关闭帧,同时不会发送更多的关闭帧。每一个终端会看到另一个终端发送过来的WebSocket 关闭状态码的状态码。像这样的,在这个示例里面,有可能两个终端都没有协商过WebSocket 关闭状态码,两个终端都几乎在同一时间单独开始 WebSocket 关闭握手。

7.1.6 WebSocket 连接关闭原因

像第 5.5.1 节和第 7.4 节中定义的一样,一个关闭帧可能包含一个用于关闭的表示原因的状态码,然后是 UTF-8 编码的数据,数据的解析方式是留给终端来解释,而不在这个协议中定义。一个正在关闭中的 WebSocket 连接可能是同时从另一端开始的。WebSocket 连接关闭原因是实现了该协议的应用收到的紧跟在状态码(第 7.4 节)之后的包含在第一个关闭控制帧中的 UTF-8 编码数据。如果在关闭控制帧中没有这些数据,那么WebSocket 连接关闭原因的值就是一个空字符串。

注:和在第 7.1.5 中被提到的逻辑一样,两个终端可能没有协商过WebSocket 连接关闭原因。

7.1.7 WebSocket 连接失效

某些算法和规范要求终端有WebSocket 连接失效。为了实现这些,客户端必须关闭 WebSocket 连接,并且可以用一个合适的方式向用户上报相关问题(尤其是对开发者有帮助的内容)。相似的,为了实现这个,服务端必须关闭 WebSocket 连接,并且应该用日志记录这个问题。

如果在此之前WebSocket 已经建立连接,此时终端需要让WebSocket 连接失效,那么在进行关闭 WebSocket 连接之前,终端需要发送一个包含恰当的状态码(第 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.4 状态码

当关闭一个连接时(如:在开始握手已经完成后,发送一个关闭帧),终端可能会说明关闭的原因。终端的这个原因的描述和终端应该采取的行动,在这个文档中都没有说明。这个文档提前定义了一些可能用于扩展、框架和终端应用的状态码和状态码范围。这些状态码和任何有关联的的文本消息在关闭帧中都是可选的。

7.4.1 定义状态码

在发送一个关闭帧时,终端可以提前定义如下的状态码。
1000
1000 表示一个正常的关闭,意味着连接建立的目标已经完成了。

1001
1001 表示终端已经“走开”,例如服务器停机了或者在浏览器中离开了这个页面。

1002
1002 表示终端由于协议错误中止了连接。

1003
1003 表示终端由于收到了一个不支持的数据类型的数据(如终端只能怪理解文本数据,但是收到了一个二进制数据)从而关闭连接。

1004
保留字段。这意味着这个状态码可能会在将来被定义。

1005
1005 是一个保留值并且不能被终端当做一个关闭帧的状态码。这个状态码是为了给上层应用表示当前没有状态码。

1006
1006 是一个保留值并且不能被终端当做一个关闭帧的状态码。这个状态码是为了给上层应用表示连接被异常关闭如没有发送或者接受一个关闭帧这种场景的使用而设计的。

1007
1007 表示终端因为收到了类型不连续的消息(如非 UTF-8 编码的文本消息)导致的连接关闭。

1008
1008 表示终端是因为收到了一个违反政策的消息导致的连接关闭。这是一个通用的状态码,可以在没有什么合适的状态码(如 1003 或者 1009)时或者可能需要隐藏关于政策的具体信息时返回。

1009
1009 表示终端由于收到了一个太大的消息无法进行处理从而关闭连接。

1010
1010 表示终端(客户端)因为预期与服务端协商一个或者多个扩展,但是服务端在 WebSocket 握手中没有响应这个导致的关闭。需要的扩展清单应该出现在关闭帧的原因(reason)字段中。

1001
1001 表示服务端因为遇到了一个意外的条件阻止它完成这个请求从而导致连接关闭。

1015
1015 是一个保留值,不能被终端设置到关闭帧的状态码中。这个状态码是用于上层应用来表示连接失败是因为 TLS 握手失败(如服务端证书没有被验证过)导致的关闭的。

7.4.2 保留状态码范围

0-999
0-999 的状态码都没有被使用。

1000-2999
1000-2999 的状态码是在这个文档、将来的修订和扩展中定义的保留字段,用于永久的可用的公共文档。

3000-3999
3000-3999 的状态码是保留给库、框架和应用使用的。这些状态码被IANA直接注册了。这些状态码在这篇文档中没有进行解释。

4000-4999
4000-4999 的状态码是保留下来私用的,因此这些状态码不能被注册。这些状态码可以使用在 WebSocket 应用之前的协议上。这些状态码在这篇文档中没有进行解释。


第八章--错误处理(Error Handling)

概述

本文为 WebSocket 协议的第八章,本文翻译的主要内容为 WebSocket 错误处理相关内容。

错误处理

8.1 处理 UTF-8 数据错误

当终端按照 UTF-8 的格式来解析一个字节流,但是发现这个字节流不是 UTF-8 编码,或者说不是一个有效的 UTF-8 字节流时,终端必须让 WebSocket 连接关闭。这个规则在建立连接开始握手和后续的数据交换时都生效。


第九章--扩展(Extension)

概述

本文为 WebSocket 协议的第九章,本文翻译的主要内容为 WebSocket 扩展相关内容。

扩展

WebSocket 可以请求该规范中提到的扩展,WebSocket 服务端可以接受其中一些或者所有的客户端请求的扩展。服务端禁止响应客户端没有请求过的扩展。如果扩展参数需要在客户端和服务端之间进行协商,这些参数必须根据参数所应用的扩展的规范来选择。

9.1 协商扩展
客户端通过 Sec-WebSocket-Extensions 请求头字段来请求扩展,请求头字段遵守 HTTP 的规则,它的值是通过 ABNF 定义的。注意这一节是通过 ABNF 语法/规则,包括“implied *LWS rule”。如果我们客户端或者服务端在协商扩展收到了一个没有符合下面的 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

任何一个扩展凭证都必须是一个注册过的凭证。(见底 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 已知扩展
扩展为实现方式提供了一个机制,即选择使用附加功能协议。这个文档中不定义任何扩展,但是实现跨越使用单独定义的扩展。


第十章--安全性考虑(Security Considerations)

概述

本文为 WebSocket 协议的第九章,本文翻译的主要内容为 WebSocket 扩展相关内容。

这一章描述了一些 WebSocket 协议的可用的安全性考虑。这一章的小节描述了这些特定的安全性考虑。

10.1 非浏览器客户端

WebSocket 协议防止在受信任的应用例如 Web 浏览器中执行的恶意 JavaScript 代码,例如通过检查Origin头字段(见下面)。见第 1.6 节去了解更多详情。这种假设在更有能力的客户端的情况下不成立。
这个协议可以被网页中的脚本使用,也可以通过宿主直接使用。这些宿主是代表自己的利益的,因此可以发送假的Origin头字段来欺骗服务端。因此服务端对于他们正在和已知的源的脚本直接通信的假设需要消息,并且必须认为他们可能通过没有预期的方式访问。特别地,服务端不应该相信任何输入都是有效的。示例:如果服务端使用输入的内容作为一部分的 SQL 查询语句,所有的输入文本都必须在传递给 SQL 服务器时进行编码,以免服务端受到 SQL 注入攻击。

10.2 源考虑
只处理特定站点,不打算处理任何 Web 页面的数据服务器应该验证Origin字段是否是他们预期的。如果服务端收到的源字段是不接受的,那么他应该通过包含 HTTP 禁止状态码为 403 的请求响应作为 WebSocket 握手的响应。

当不信任的一方是 JavaScript 应用作者并存在受信任的客户端中运行时,Origin字段可以避免出现这种攻击的情况。客户端可以连接到服务端,通过协议中的Origin字段,确定是否开放连接的权限给 JavaScript 应用。这么做的目的不是组织非浏览器应用建立连接,而是保证在受信任的浏览器中可能运行的恶意 JavaScript 代码并不会构建一个假的 WebSocket 握手。

10.3 基础设施攻击(添加掩码)

除了终端可能会成为通过 WebSocket 被攻击的目标之外,网络基础设施的另外一部分,例如代理,也有可能是攻击的对象。

这个协议发展后,通过一个实验验证了部署在外部的缓存服务器由于一系列在代理上面的攻击导致投毒。一般形式的攻击就是在攻击者控制下建立一个与服务端的连接,实现一个与 WebSocket 协议建立连接相似的 HTTP UPGRADE 连接,然后通过升级以后的连接发送数据,看起来就像是针对已知的特定资源(在攻击中,这可能类似于广泛部署的脚本,用于跟踪广告服务网络上的点击或资源)进行 GET 请求。远端服务器可能会通过一些看上去像响应数据的来响应假的 GET 请求,然后这个响应就会按照非零百分比的已部署中介缓存,因此导致缓存投毒。这个攻击带来的影响就是,如果一个用户可以正常的访问一个攻击者控制的网网站,那么攻击者可以针对这个用户进行缓存投毒,在相同缓存的后面其他用户会运行其他源的恶意脚本,破坏 Web 安全模型。

为了避免对中介服务的此类攻击,使用不符合 HTTP 的数据帧中为应用程序的数据添加前缀是不够的,我们不可能详细的检查和测试每一个不合标准的中介服务有没有跳过这种非 HTTP 帧,或者对帧载荷处理不正确的情况。因此,采用的防御措施是对客户端发送给服务端的所有数据添加掩码,这样的话远端的脚本(攻击者)就不能够控制发送的数据如何出现在线路上,因此就不能够构造一条被中介误解的 HTPT请求。

客户端必须为每一帧选择一个新的掩码值,使用一个不能够被应用预测到的算法来进行传递数据。例如,每一个掩码值可以通过一个加密强随机数生成器来生成。如果相同的值已经被使用过或者已经存在一种方式能够判断出下一个值如何选择时,攻击这个可以发送一个添加了掩码的消息,来模拟一个 HTTP 请求(通过在线路上接收攻击者希望看到的消息,使用下一个被使用的掩码值来对数据进行添加掩码,当客户端使用它时,这个掩码值可以有效地反掩码数据)。

当从客户端开始传递第一帧时,这个帧的有效载荷(应用程序提供的数据)就不能够被客户端应用程序修改,这个策略是很重要的。否则,攻击者可以发送一个都是已知值(例如全部为 0)的初始值的很长的帧,计算收到第一部分数据时使用过的掩码,然后修改帧中尚未发送的数据,以便在添加掩码时显示为 HTTP 请求。(这与我们在之前的段落中描述的使用已知的值和可预测的值作为掩码值,实际上是相同的问题。)如果另外的数据已经发送了,或者要发送的数据有所改变,那么新的数据或者修改的数据必须使用一个新的数据帧进行发送,因此也需要选择一个新的掩码值。简短来说,一旦一个帧的传输开始后,内容不能够被远端的脚本(应用)修改。

受保护的威胁模型是客户端发送看似HTTP请求的数据的模型。因此,从客户端发送给服务端的频道数据需要添加掩码值。从服务端到客户端的数据看上去像是一个请求的响应,但是,为了完成一次请求,客户端也需要可以伪造请求。因此,我们不认为需要在双向传输上添加掩码。(服务端发送给客户端的数据不需要添加掩码)

尽管通过添加掩码提供了保护,但是不兼容的 HTTP 代理仍然由于客户端和服务端之间不支持添加掩码而受到这种类型的攻击。

10.4 指定实现的限制

在从多个帧重新组装后,对于帧大小或总消息大小具有实现和必须避免自己超过相关的多平台特定限制带来的影响。(例如:一个恶意的终端可能会尝试耗尽对端的内存或者通过发送一个大的帧(例如:大小为 2 ** 60)或发送一个长的由许多分片帧构成的流来进行拒绝服务攻击)。这些实现应该对帧的大小和组装过后的包的总大小有一定的限制。

10.5 WebSocket 客户端认证

这个协议在 WebSocket 握手时,没有规定服务端可以使用哪种方式进行认证。WebSocket 服务器可以使用任意 HTTP 服务器通用的认证机制,例如: Cookie、HTTP 认证或者 TLS 认证。

10.6 连接保密性和完整性

连接保密性是基于运行 TLS 的 WebSocket 协议(wss 的 URLs)。WebSocket 协议实现必须支持 TLS,并且应该在与对端进行数据传输时使用它。
如果在连接中使用 TLS,TLS带来的连接的收益非常依赖于 TLS 握手时的算法的强度。例如,一些 TLS 的加密算法不提供连接保密性。为了实现合理登记的保护措施,客户端应该只使用强 TLS 算法。“Web 安全:用户接口指南”(W3C.REC-wsc-ui-20100812)讨论了什么是强 TLS 算法。RFC5246 的附录 A.5和附录 D.3提供了另外的指导。

10.7 处理无用数据

传入的数据必须经过客户端和服务端的认证。如果,在某个时候,一个终端面对它无法理解的数据或者违反了这个终端定义的输入安全规范和标准,或者这个终端在开始握手时没有收到对应的预期值时(在客户端请求中不正确的路径或者源),终端应该关闭 TCP 连接。如果在成功的握手后收到了无效的数据,终端应该在进入关闭 WebSocket流程前,发送一个带有合适的状态码(第 7.4 节)的关闭帧。使用一个合适的状态码的关闭帧有助于诊断这个问题。如果这个无效的数据是在 WebSocket 握手时收到的,服务端应该响应一个合适的 HTTP 状态码(RFC2616)。

使用错误的编码来发送数据是一类通用的安全问题。这个协议指定文本类型数据(而不是二进制或者其他类型)的消息使用 UTF-8 编码。虽然仍然可以得到长度值,但实现此协议的应用程序应使用这个长度来确定帧实际结束的位置,发送不合理的编码数据仍然会导致基于此协议构建的应用程序可能会导致从数据的错误解释到数据丢失或潜在的安全漏洞出现。

10.8 在 WebSocket 握手中使用 SHA-1

在这个文档中描述的 WebSocket 握手协议是不依赖任意 SHA-1 的安全属性,流入抗冲击性和对第二次前映像攻击的抵抗力(就像 RFC4270 描述的一样)。


第十一章--IANA 注意事项(IANA Considerations)

概述

本文为 WebSocket 协议的第十一章,本文翻译的主要内容为 WebSocket 的 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 协议打开一个连接。

编码注意事项
按照上面定义的语法排除的主机部分中的字符必须按照 RFC3987 中的规定从 Unicode 转换为 ASCII 或其替换字符。为了实现基于方案的规范化,国际化网域名称(IDN)主机组件的形式与 punycode 码之间的相互转化认为是等价的(见 RFC3987 第 5.3.3 节)。

除了由上面语法排除的字符外,其他组件的字符在第一次转换为 UTF-8 字符时,必须从 Unicode 码转化到 ASCII 码,然后使用百分比编码格式替换对应的定义在 URI RFC3896 字符和国际化资源标识符(IRI) RFC3987 规范。

应用/协议使用这个 URI 协议规范
WebSocket Protocol
互操作性注意事项
使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。
安全性注意事项
见”安全性注意事项”一节。
联系
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 加密。

编码注意事项
按照上面定义的语法排除的主机部分中的字符必须按照 RFC3987 中的规定从 Unicode 转换为 ASCII 或其替换字符。为了实现基于方案的规范化,国际化网域名称(IDN)主机组件的形式与 punycode 码之间的相互转化认为是等价的(见 RFC3987 第 5.3.3 节)。
除了由上面语法排除的字符外,其他组件的字符在第一次转换为 UTF-8 字符时,必须从 Unicode 码转化到 ASCII 码,然后使用百分比编码格式替换对应的定义在 URI RFC3896 字符和国际化资源标识符(IRI) RFC3987 规范。

应用/协议使用这个 URI 协议规范
WebSocket Protocol

互操作性注意事项
使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。

安全性注意事项
见”安全性注意事项”一节。
联系
HYBI WG <hybi@ietf.org\\\>
作者/更改控制者
IETF <iesg@ietf.org\\\>
关联
RFC 6455

11.2 注册“WebSocket”协议升级关键值

这一节描述一个在 HTTP 升级凭证注册的关键值,在 RFC2817 定义。
凭证名称
WebSocket
作者/更改控制者
IETF <iesg@ietf.org\\\>
关联
RFC 6455

11.3 注册新的 HTTP 头字段
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 响应中不能出现超过1次。

11.3.3 Sec-WebSocket-Accept
这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。
头字段名称
Sec-WebSocket-Accept
应用协议
http
状态
标准
作者/更改控制者
IETF
说明文档
RFC 6455
关联信息

这个头字段只用于 WebSocket 开始握手。
Sec-WebSocket-Accpet 头字段是用于 WebSocket 开始握手阶段。它是通过服务端发送给客户端,用来确认服务端会初始化一个 WebSocket 连接。Sec-WebSocket-Accpet 头在一个 HTTP 响应中不允许出现超过1次。

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 响应中不能出现超过1次。

11.3.5 Sec-WebSocket-Version
这一节描述一个注册在永久消息头字段名称中的头字段,在 RFC3864 定义。
头字段名称
Sec-WebSocket-Version
应用协议
http
状态
标准
作者/更改控制者
IETF
说明文档
RFC 6455
关联信息
这个头字段只用于 WebSocket 开始握手。

Sec-WebSocket-Version 头字段是用于 WebSocket 开始握手阶段。它是从客户端发送给服务端来表示这个连接使用的协议版本。它能够让服务端正确的进行开始握手和接下来的数据发送,以及在服务端不能够在一个安全方式下正确解析数据时关闭连接。Sec-WebSocket-Version 头字段在服务端理解的版本不匹配从客户端收到的版本导致的 WebSocket 握手失败时,也从服务端发送给客户端。在这种情况下,这个头字段包含服务端支持的协议版本。注意这里不期望更高的版本号需要向前兼容低版本号。

Sec-WebSocket-Version 头字段可以在一个 HTTP 响应中出现多次(这个逻辑等价于一个单独的Sec-WebSocket-Version包含所有的值)。然而,Sec-WebSocket-Version 头字段不能在 HTTP 请求中出现超过1次。

11.4 WebSocket 扩展名注册表

这个规范根据RFC5526中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。
作为此注册表的一部分,IANA 包含了一下信息:
扩展定义
这个扩展的定义,将在 Sec-WebSocket-Extensions 头字段中使用,在此规范的第 11.3.2 节注册。这个值必须满足在此规范第 9.1 节中定义的扩展凭证要求。
扩展通用名
扩展名称,一般称为扩展名。
扩展定义
对定义与 WebSocket 协议一起使用的扩展的文档的引用。
已知不兼容扩展
已知的不兼容的扩展定义列表。
WebSocket 扩展名是受到“先到先得” IANA 注册政策 RFC5226 限制的。
这个注册表里没有初始值。

11.5 WebSocket 子协议名注册表
这个规范根据RFC5526中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。
作为此注册表的一部分,IANA 包含了一下信息:
子协议定义
子协议的标识符将在 Sec-WebSocket-Protocol 头字段中使用,在此规范的第 11.3.4 节中注册。这个之必须符合此规范第 4.1 节中的第 10  项要求—换句话说,这个之必须是 RFC2616 中定义的凭证。

子协议通用名
子协议名称,通常被称为子协议。

子协议定义
对定义与 WebSocket 协议一起使用的子协议的文档的引用。

WebSocket 子协议名是受到“先到先得” IANA 注册政策 RFC5226 限制的。

11.6 WebSocket 版本号注册表

该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 版本号。作为此注册表的一部分,IANA 包含了一下信息:
版本号
版本号是用于在此规范第 4.1 节中制定的 Sec-WebSocket-Version 字段。这个值必须是一个范围在 0 到 255(含)之间的非负整数。

参考
RFC 请求新版本号或者带有版本号的草稿名称(见下文)。

状态
临时或者标准。见下面描述。

版本号被指定为“临时”或者“标准”。

“标准”的版本号被记录在 RFC 文档中,被认为是一个重大、稳定的 WebSocket 协议版本,例如定义在这个 RFC 中的版本。“标准”版本号是受到 “IETF 评论” IANA 注册政策 RFC5526 限制的。
“临时”版本是记录在网络草案和用于帮助实现者识别 WebSocket 协议的已部署版本并与之互操作,例如开发后但是发布前的 RFC 版本。“临时”版本号是受到 “专家评论” IANA 注册政策 RFC5526 、 最初的指定专家如HYBI 工作组主席(或者,如果工作组关闭,那么是 IETF 应用领域的领域主任)限制的。

IANA 已经向注册表中添加了如下的初始值。
版本号引用状态
0draft-ietf-hybi-thewebsocketprotocol-00临时
1draft-ietf-hybi-thewebsocketprotocol-01临时
2draft-ietf-hybi-thewebsocketprotocol-02临时
3draft-ietf-hybi-thewebsocketprotocol-03临时
4draft-ietf-hybi-thewebsocketprotocol-04临时
5draft-ietf-hybi-thewebsocketprotocol-05临时
6draft-ietf-hybi-thewebsocketprotocol-06临时
7draft-ietf-hybi-thewebsocketprotocol-07临时
8draft-ietf-hybi-thewebsocketprotocol-08临时
9保留 
10保留 
11保留 
12保留 
13RFC6455 


11.7 WebSocket 关闭码注册表

该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 关闭码。作为此注册表的一部分,IANA 包含了一下信息:
状态码
状态码表示定义在此文档第 7.4 节中 WebSocket 连接关闭的原因。这个状态码是一个在 1000 到 4999(含)之间的整数。

含义
状态码含义。每一个状态码有一个特定的含义。

联系
保留状态代码的实体的联系人。

关联
请求状态码的固定文档和含义定义。对于 1000-2999 的状态码来说是必须的,推荐使用 3000-3999 范围的状态码。

WebSocket 关闭状态码根据它的范围有不同的注册要求。使用在这个协议和它的后续的版本或者扩展的请求版本号是受到“标准行为”、“规范要求”(这意味着“指定专家”)或者“IESG 评论” IANA注册表政策限制的,应该在 1000-2999 范围内授权。被类库、框架和应用使用的状态码是受限制于“先到先得”IANA 注册表政策,应该在 3000-3999 范围内授权。4000-4999 范围的状态码是私用的。请求应指明它们是否正在通过扩展、类库、框架或者应用使用请求WebSocket协议的状态代码(或者将来的协议的版本)。
IANA已经向注册表中添加了如下初始值。
状态码含义联系人关联
1000正常关闭hybi@ietf.orgRFC6455
1001离开hybi@ietf.orgRFC6455
1002协议错误hybi@ietf.orgRFC6455
1003不支持的数据类型hybi@ietf.orgRFC6455
1004保留hybi@ietf.orgRFC6455
1005没有收到状态码hybi@ietf.orgRFC6455
1006异常关闭hybi@ietf.orgRFC6455
1007无效的帧数据hybi@ietf.orgRFC6455
1008违反政策hybi@ietf.orgRFC6455
1009消息太大hybi@ietf.orgRFC6455
1010强制扩展hybi@ietf.orgRFC6455
1011内部服务器错误hybi@ietf.orgRFC6455
1015TLS握手hybi@ietf.orgRFC6455


11.8 WebSocket 操作码注册表

该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 操作码。
作为此注册表的一部分,IANA 包含了一下信息:
操作码
操作码表示定义在第 5.2 节中的 WebSocket 帧的帧类型。操作码是一个范围在 0 到 15(含)的数字。

含义
操作码的含义。

关联
请求操作码的规范。

WebSocket 操作码是受到“标准行为”IANA 注册表政策 RFC5266 限制的。IANA 已经向注册表中注册了一下初始值。
操作码含义关联
0连续帧RFC6455
1文本帧RFC6455
2二进制帧RFC6455
8连接关闭帧RFC6455
9心跳 Ping 帧RFC6455
10心跳 Pong 帧RFC6455


11.9 WebSocket 帧头 bit 字段注册表

该规范根据 RFC5226 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 帧头 bit 字段。这个注册表控制分配的 bit 位为第 5.2 节中的 RSV1、RSV2 和 RSV3。
这些 bit 位是保留给将来的版本或者文档中的扩展。

WebSocket 帧头 bit 字段是受到“标准行为”IANA 注册表政策 RFC5266 限制的。


第十二章--使用其他规范中的WebSocket协议

概述

本文为 WebSocket 协议的第十二章,本文翻译的主要内容为如何使用其他规范中的 WebSocket 协议。

WebSocket协议旨在由另一规范使用,以提供动态作者定义内容的通用机制。例如,在定义脚本 API 的规范中定义 WebSocket 协议。例如一个规范首先需要建立 WebSocket 连接,提供该算法:
目标资源,包含一个主机名(host)和一个端口(port)。
资源名称,允许在一个主机和端口上识别多个服务。
安全标记,当这个值为 true 时,连接应该被加密,如果为 false 时则不需要。
原始RFC6454的ASCII序列化,负责连接。
可选的,基于 WebSocket 连接的通过一个字符串定义的协议。

主机、端口、资源名称和安全标记通常是使用解析 WebSocket URI 组件,通过 URI 来获取。如果 URI 中没有指定这些 WebSocket 字段,那么这个解析将失败。
如果在任意时间连接被关闭了,那么规范需要使用关闭 WebSocket 连接算法(第 7.1.1 节)。

第 7.1.4 节定义了什么时候WebSocket 连接关闭。当连接打开时,文档需要处理收到一条 WebSocket 消息(第 6.2 节)的场景。为了向已经建立的连接发送一些数据,文档需要处理发送 WebSocket 消息(第 6.1 节)。


第十三章--致谢


特别感谢Ian Hickson,因为他是本协议的原始作者和编辑。本规范的初始设计受益于WHATWG 和WHATWG 邮件列表的许多人的参与。对规范的贡献没有按照章节跟踪,但在WHATWG HTML规范 http://whatwg.org/html5 中给出了给规范贡献的所有人列表。

也特别感谢John Tamplin为本规范的“数据帧”提供了大量的文字。

也特别感谢Adam Barth提供了大量的文字和“数据掩码”章节的背景研究。

特别感谢Lisa Dusseault为应用区(Apps Area)复审(和帮助启动这个工作),Richard Barnes为Gen-Art复审,和Magnus Westerlund为传输区(Transport Area)复审。特别感谢HYBI WG和过去和现在的WG主席:Joe Hildebrand、Salvatore Loreto和Gabriel Montenegro,他们不知疲倦地在幕后工作,直到这项工作完成。最后,但并非最不重要的,特别感谢负责区域董事(Area Director)Peter Saint-Andre。

感谢在HYBI WG邮件列表参与讨论的和贡献想法和/或提供细节复审的以下人员(列表并不是完整的):Greg Wilkins, John Tamplin, Willy Tarreau, Maciej Stachowiak, Jamie Lokier, Scott Ferguson, Bjoern Hoehrmann, Julian Reschke, Dave Cridland, Andy Green, Eric Rescorla, Inaki Baz Castillo, Martin Thomson, Roberto Peon, Patrick McManus, Zhong Yu, Bruce Atherton, Takeshi Yoshino, Martin J. Duerst, James Graham, Simon Pieters, Roy T. Fielding, Mykyta Yevstifeyev, Len Holgate, Paul Colomiets, Piotr Kulaga, Brian Raymor, Jan Koehler, Joonas Lehtolahti, Sylvain Hellegouarch, Stephen Farrell, Sean Turner, Pete Resnick, Peter Thorson, Joe Mason, John Fallows, 和Alexander Philippou。注意,上边列出的人员并不一定赞同这些工作的最终结果。


第十四章--参考文献


14.1.参考标准

[ANSI.X3-4.1986]
American National Standards Institute, "Coded Character Set - 7-bit American Standard Code for Information Interchange", ANSI X3.4, 1986.

[FIPS.180-3]
National Institute of Standards and Technology, "Secure Hash Standard", FIPS PUB 180-3, October 2008,< http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf >.

[RFC1928]
Leech, M., Ganis, M., Lee, Y., Kuris, R., Koblas, D., and L. Jones, "SOCKS Protocol Version 5", RFC 1928,March 1996.

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.

[RFC2616]
Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, "HypertextTransfer Protocol -- HTTP/1.1", RFC 2616, June 1999.

[RFC2817]
Khare, R. and S. Lawrence, "Upgrading to TLS Within HTTP/1.1", RFC 2817, May 2000.

[RFC2818]
Rescorla, E., "HTTP Over TLS", RFC 2818, May 2000.

[RFC3629]
Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.

[RFC3864]
Klyne, G., Nottingham, M., and J. Mogul, "Registration Procedures for Message Header Fields", BCP 90, RFC 3864, September 2004.

[RFC3986]
Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, January 2005.

[RFC3987]
Duerst, M. and M. Suignard, "Internationalized Resource Identifiers (IRIs)", RFC 3987, January 2005.

[RFC4086]
Eastlake, D., Schiller, J., and S. Crocker, "Randomness Requirements for Security", BCP 106, RFC 4086, June 2005.

[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, October 2006.

[RFC5226]
Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 5226, May 2008.

[RFC5234]
Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, January 2008.

[RFC5246]
Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, August 2008.

[RFC6066]
Eastlake, D., "Transport Layer Security (TLS) Extensions: Extension Definitions", RFC 6066, January 2011.

[RFC6454]
Barth, A., "The Web Origin Concept", RFC 6454, December 2011.

14.2.参考资料

[RFC4122]
Leach, P., Mealling, M., and R. Salz, "A Universally Unique IDentifier (UUID) URN Namespace", [RFC 4122}(http://tools.ietf.org/html/rfc4122), July 2005.

[RFC4270]
Hoffman, P. and B. Schneier, "Attacks on Cryptographic Hashes in Internet Protocols", RFC 4270, November 2005.

[RFC5321]
Klensin, J., "Simple Mail Transfer Protocol", RFC 5321, October 2008.

[RFC6202]
Loreto, S., Saint-Andre, P., Salsano, S., and G. Wilkins, "Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP", RFC 6202, April 2011.

[RFC6265]
Barth, A., "HTTP State Management Mechanism", RFC 6265, April 2011.

[TALKING]
Huang, L-S., Chen, E., Barth, A., Rescorla, E., and C. Jackson, "Talking to Yourself for Fun and Profit", 2010, < http://w2spconf.com/2011/papers/websocket.pdf >.

[W3C.REC-wsc-ui-20100812]
Roessler, T. and A. Saldhana, "Web Security Context: User Interface Guidelines", World Wide Web Consortium Recommendation REC-wsc-ui-20100812, August 2010, < http://www.w3.org/TR/2010/REC-wsc-ui-20100812/ >.

Latest version available at < http://www.w3.org/TR/wsc-ui/ >.

[WSAPI]
Hickson, I., "The WebSocket API", W3C Working Draft WD-websockets-20110929, September 2011, < http://www.w3.org/TR/2011/WD-websockets-20110929/ >.

Latest version available at <http://www.w3.org/TR/websockets/ >.

[XMLHttpRequest]
van Kesteren, A., Ed., "XMLHttpRequest", W3C Candidate Recommendation CR-XMLHttpRequest-20100803, August 2010,< http://www.w3.org/TR/2010/CR-XMLHttpRequest-20100803/ >.

Latest version available at < http://www.w3.org/TR/XMLHttpRequest/ >.

作者地址
Ian Fette
Google, Inc.
EMail: ifette+ietf@google.com
URI: http://www.ianfette.com/

Alexey Melnikov
Isode Ltd.
5 Castle Business Village
36 Station Road
Hampton, Middlesex TW12 2BX UK
EMail: Alexey.Melnikov@isode.com



参考链接

WebSocket 协议 RFC 文档(全中文翻译)

websocket协议翻译


感谢原作者及译者。