ALL2CN#RFC#RFC6455 WebSocket 协议

生来就可爱ヽ(ⅴ<●) 提交于 2020-11-05 08:57:17

原文地址:https://tools.ietf.org/html/rfc6455 中文翻译地址:https://juejin.im/post/6844903779192569869

摘要

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

备忘录状态

这是一个互联网标准跟踪文档。

这个文档是由互联网工程任务组(IETF,Internet Engineering Task Force)产出的。它代表了互联网工程任务组社区的共识。这个文档已经征求过公众的意见并且互联网工程指导小组(IESG,Internet Engineering Steering Group)已经同意发布。更多关于互联网标准的信息在[RFC 5741的第二节][3]可以看到。

关于这篇文档当前状态的信息和勘误表,以及如何进行反馈可以在[此处][4]查看。

版权声明

2011年授权给IETF授信和被认为是文档作者的人。保留所有权利。

这个文档适用于[BCP 78][5]和IETF之前的相关[IETF文档][6]都在此文档发布日期生效。请细心阅读这些文档,他们说明了对于这篇文档你的权利和限制。从此文档中提取的代码组件必须包含如第四节所述的法律规定的简化的BSD许可协议文本。

1 介绍

本章为协议正文内容的第一章(Introduction)。

1.1 背景

此章节为非规范章节。

在历史上,创建一个客户端和服务端的双向数据Web应用(例如IM应用和游戏应用)需要向服务端频繁发送不同于一般[HTTP请求][1]的HTTP轮询请求来从服务端上游更新数据。

这个方法有许多的问题:

  • 服务端被迫使用大量的的潜在的TCP连接与客户端进行交互:一部分是用来发送数据,而另一部分是用来接收数据。
  • 应用层无线传输协议(HTTP)开销较大,每一个客户端到服务端的消息都有一个HTTP头。
  • 客户端脚本必须包含一个发送和接收对应的映射表来进行对应数据处理。

一个简单的解决方案是使用一个简单的TCP链接来进行双向数据传输。这就是WebSocket提供的能力。结合WebSocket的[API][2],它能够提供一个可以替代HTTP轮询的方法来满足Web页面和远端服务器的双向数据通信。

相同的技术可以被用到许多的Web应用:游戏、股票应用、多人协作应用、与后端服务实时交互的用户接口等。

WebSocket协议设计的原因是取代已经存在的使用HTTP作为传输层的双向通信技术,从而使得已经存在的基础服务(如代理、过滤器、认证服务)能够受益。这种技术是基于效率和可靠性权衡后来进行实现的,而HTTP协议最初也[不是用来做双向数据通信的][3]。WebSocket协议尝试实现基于现有的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

客户端请求的第一行(leading line)遵循了HTTP请求行的格式。

服务端的第一行(leading line)遵循了HTTP状态行的格式。

HTTP请求行和状态行的规范定义在[RFC2616][4]。

在两个协议中,第一行header下面是一组无序的header字段。这些header字段包含的内容在本文的[第四节][5]。另外的header字段如[cookies][6],也有可能存在。格式和解析头信息被定义在了[RFC2616][7]。

当客户端和服务端都发送了他们的握手协议,并且当握手已经成功,那么数据传输就开始了。这是一个双方都可以独立发送任意数据的双向通信渠道。

在握手成功以后,客户端和服务端传输的数据来回传输的数据单位,我们在规范中称为消息(messages)。在传输中,一条消息有一个或者多个帧组成。WebSocket中的消息不需要对应特定网络层中的帧,一条零散的消息可能由中间人合并或者拆分成网络层的帧。

帧有关联的类型。同一条消息的每一帧都包含相同类型的数据。通常来说,它可以是文本数据(UTF-8编码)、二进制数据(留给应用解析的数据)和控制帧数据(不是用来传输数据,而是用来作为协议层的特定符号,如关闭连接帧)。当前版本的协议定义了6种控制帧类型并且预留了10个保留类型。

1.3 开始握手

此节为非规范章节。

开始握手为了与基于HTTP的服务端软件和中介兼容,因此一个独立的端口既能够同时满足HTTP客户端来与服务进行交互,又能够满足WebSocket客户端与服务进行交互。最终,WebSocket客户端的握手是一个基于HTTP的升级请求:

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][8],客户端在握手过程中发送的header字段可能是乱序的,所以收到的header字段的顺序不同也没有太大影响。

GET方法的请求URI(Request-URI)是用于定义WebSocket连接的终端,允许同一个IP对多个域名提供服务,也允许多个WebSocket终端连接同一个服务器。

客户端在每一个握手的Hostheader里面包含了一个主机域名。所以客户端和服务端都可以校验哪些域名在使用中。

另外的header字段是用来确定WebSocket协议的选项。这个版本中提供的特定选项是子协议选择(Sec-WebSocket-Protocol)、客户端支持的扩展列表(Sec-WebSocket-Extensions)、Originheader字段等。请求header字段Sec-WebSocket-Protocol可以用来标识哪些子协议(基于WebSocket的应用高层协议)是客户端可以支持的。服务端会从中选择零个或者一个支持的协议并且在响应握手中输出它选择的那个协议。

Sec-WebSocket-Protocol: chat

服务端也可以设置cookie相关字段来设置cookie相关属性,具体文档见[RFC6265][9]。

1.4 结束握手

此节为非规范章节。

结束握手远比连接握手简单。

任何一端都可以发送一个包含特定关闭握手的控制帧数据(详情见5.5.1节)。收到此帧后,另一端在不发送任何数据后会发送一个结束帧作为响应。收到另一端的结束帧后,最开始发送控制帧的端在没有数据需要发送时,就会安全的关闭此连接。

在发送了一个表明连接需要被关闭的控制帧后,这个客户端不会再发送任何的数据;在收到一个表明连接需要被关闭的控制帧后,这个客户端会丢弃此后的所有数据。

这样比两边同时发起握手要更加安全。

这个结束握手的目标是来补充TCP结束握手中的一些内容(FIN/ACK),而这是因为TCP结束握手在端与端之间并不一定可靠,尤其是有代理和其他的网络中介时会变得不可靠。

在发送关闭帧等待接受另一端的响应关闭帧时,在某些情况下可以避免数据的不必要丢失。例如,在某些平台中,如果一个socket在接收队列有数据时被关闭,会发送一个RST包,尽管数据还在等待被读取,这也会导致接收到RST的一方数据接收失败。

1.5 设计哲学

此节为非规范章节。

WebSocket协议设计的原理,将框架最小化,对框架的唯一的约束就是使这个协议是基于帧而不是流并且可以支持Unicode文本和二进制帧两者中的任意一种。在基于WebSocket的应用层中,元数据是应该分层的,就像基于TCP的应用层(例如HTTP)一样。

从概念上来看,WebSocket层是基于TCP实现的,增加了以下的内容:

  • 增加了一个基于浏览器的同源策略模型
  • 增加了一个地址和协议命名机制用以在同一个端口上支持多个服务,在同一个IP地址自持多个主机名
  • 在TCP协议上分层构建框架机制回到TCP使用的IP包机制,但是没有长度限制
  • 包含一个设计用于处理有代理和其他网络中介的情况的额外的结束握手协议

除此之外,WebSocket没有增加任何东西。基本上WebSocket的的目标是在约束的条件下向脚本提供尽可能接近原生的TCP的Web服务。它同时考虑了服务器在进行握手和处理有效的HTTP升级请求时,可以和HTTP共用一个服务。大家也可以使用其他协议来建立从客户端到服务端的消息通信,但WebSocket的协议的目的是为了提供一个相对简单的可以和HTTP共存,并且依赖于HTTP基础设施(如代理)的协议。这个非常接近TCP的协议因为基于安全的基础设施和针对性的能够简单使用和让事情变得更简单的补充(例如消息语义的补充),因此可以安全使用。

这个协议具有可扩展性,未来的版本可能会引入一些新的概念如多路复用。

1.6 安全模型

此节为非规范章节。

当WebSocket协议在web网页中应用时,WebSocket协议在Web页面与WebSocket服务器建立连接时使用基于web浏览器的同源策略模型。所以说,当WebSocket协议在一个特定的客户端(不是web浏览器里面的网页)直接使用时,同源策略模型就不生效了,客户端可以接受任意的源数据。

该协议无法与已经存在的如SMTP([RFC5421][10])和HTTP协议的服务器建立连接,如果需要的话,HTTP服务器可以选择支持该协议。该协议还实现了严格约束的握手过程和限制数据不能在握手完成和建立连接之前插入数据进行传输(因此限制了许多被影响的服务器)。

WebSocket服务器同样无法与其他协议尤其是HTTP建立连接。例如,一个HTML“表单”可能会提交给一个WebSocket服务器。WebSocket服务端只能读取包含特定的由WebSocket客户端发送的字段的握手数据。尤其是在编写这个规范时,攻击者不能只使用HTML和JavaScript APIs的Web浏览器来发送以Sec-开头的字段。

1.7 与TCP和HTTP的关系

此节为非规范章节。

WebSocket协议是独立的基于TCP的协议。他和HTTP的唯一关系是建立连接的握手操作的升级请求是基于HTTP服务器的。

WebSocket默认使用80端口进行连接,而基于TLS([RFC2818][11])的WebSocket连接是基于443端口的。

1.8 建立连接

此节为非规范章节。

当建立了一个和HTTP服务器共享端口的连接时(这种情况很有可能发送在与80和443端口通信上),这个链接将会给HTTP服务器发送一个常规的GET请求来进行升级。在一个IP地址和一个单一的服务器来应对单一主机名的通信这种相对简单的设置上,基于WebSocket协议的系统可以通过一个更加实用的方法来进行部署。在更详细的设置(例如负载均衡和多服务器),与HTTP服务器分开的专属的WebSocket连接集群可能更加易于管理。在编写这个规范时,我们应该知道在80端口和443端口建立WebSocket连接的成功率是不同的,在443端口上面建立的连接很明显更容易成功,尽管这可能随着时间的变化而改变。

1.9 使用WebSocket协议的子协议

客户端可以通过在握手阶段中的Sec-WebSocket-protocol字段来请求服务端使用指定的子协议。如果指定了这个字段,服务器需要包含相同的字段,并且从子协议的之中选择一个值作为建立连接的响应。

子协议的名称可以按照[第11.5节][12]的方法进行注册。为了避免潜在的冲突,推荐使用包含ASCII码的域名名称作为子协议名。例如,Example Corporation创造了在Web上通过多个服务器实现的一个聊天子协议(Chat subprotocol),他们可以叫做chat.example.com。如果Example Organization创造了他们相对的子协议叫做chat.example.org,这两个子协议可以被服务器同时实现,服务器可以根据客户端来动态的选择使用哪一个子协议。

子协议也可以通过修改名字的方式来向后兼容,例如:将bookings.example.net改为v2.bookings.example.net。WebSocket客户端能够完全的区分这些子协议。向后兼容的版本控制可以通过复用相同的子协议字符和小心设计的子协议实现来保证这种扩展性。

2 一致性要求(第二章协议正文)

在这篇文档中,所有的图、示例和笔记都是非规范性的,就像标注了非规范性的所有章节一样。在文档中没有指定的其他内容都是规范性的。

在这篇文档中的关键词如“必须(MUST)”、“必须不(MUST NOT)”、“需要(RWQUIRE)”、“应该(SHALL)”、“不应该(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“也许(MAY)”和“可选(OPTIONAL)”可以按照RFC2119 所述进行解释。

作为算法的一部分的命令式语句(如“删除任何前导空格”或“返回false并且中止后续步骤”)在介绍算法时应该与关键词一起解释(“必须(MUST)”、“应该(SHOULD)”、“也许(MAY)”等)。

算法或者指定步骤中的符合要求的措辞可以通过任何方式表述,只要最终的结果是等价的。(尤其是在算法定义中,我们的目标是竟可能简单的操作而不是最求完美。)

2.1 术语和其他公约

_ASCII_表示定义在[ANSI.X3-4.1986][1]的字符编码表。

这个文档参考UTF-8的值,使用在STD 63([RFX3629][2])定义的UTF-8标准格式。

如命名算法或者定义关键输入的标识如_this_。

命名header字段或者变量如|this|。

本文引用了WebSocket连接失败(_Fail the WebSocket Connection_)这个程序。这个程序位于第7.1.7节。

转换小写字符(_Converting a string to ASCII lowercase_)意味着替换从U+0041到U+005A的所有字符(拉丁字母大写A到Z)为相对应的U+0061到U+007A的字符(拉丁字母小写A-Z)。

不区分ASCII大小写(_ASCII case-insensitive_)比较方式意味着通过码点(code point)比较这两个字符,如果这两个字符是U+0041到U+005A(拉丁字母大写A到Z)和相对应的U+0061到U+007A的字符(拉丁字母小写A-Z),那么也认为这两个字符相等。

文档中URI这个词被定sj义在了[RFC3986][3]。

当需要实现WebSocket协议中一部分的_send_数据时,这个实现是有可能会延迟任意时间来进行数据传输的,例如,使用数据缓冲区来保证发送较少的IP数据包。

这个文档在不同的章节会同时使用[RFC5234][4]和[RFC2616][5]这两个中的扩充巴科斯-瑙尔范式(ABNF)。

WebSocket URIs(第三章协议正文)

这个规范使用在[RFC5234][1]中的ABNF语法以及URI规范中的[RFC3986][2]的术语和ABNF产品定义了两套方案。

ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

host = <host, defined in [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.2.2), 3.2.2节>
port = <port, defined in [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.2.3), 3.2.3节>
path = <path-abempty, defined in [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.3), 3.3节>
query = <query, defined in [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.4), 3.4节>

端口字段是可选的,默认的"ws"端口是80,而默认的"wss"端口是443。

命中不论大小写的"wss"方案字段就表明这个URI可以被称为安全的(已经设置安全标记)。

"resource-name"字段(在4.1节也被称为/resouce name/字段)可以通过以下的数据串联起来:

  • "/",表示路径(path)字段为空
  • 路径(path)字段
  • "?",表示非空的查询参数(query)
  • 空查询参数(query)

在WebSocket URIs的里,身份标识片段是没有意义的,而且禁止使用在这些URI里面。与任何的URI方案一样,"#"字符不是表示片段(fragment)开始时,都必须编码为%23

4 开始握手(协议正文)

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.comb.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][1])代理供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][2]指定的有效的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. 请求必须包含一个Hostheader字段,它包含了一个主机(host)字段加上一个紧跟在":"之后的端口(port)字段(如果端口不存在则使用默认端口)。

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

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

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

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

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

    例如,如果在www.example.com域下面的代码尝试与ww2.example.com这个地址建立连接,那么这个header字段的值就应该是"http://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][5]中的分隔符,并且每个协议必须是一个唯一的字符串。ABNF的这个header字段的值是在[RFC2616][6]定义了构造方法和规则的1#token。

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

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

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

  1. 如果客户端收到的服务端返回状态码不是101,客户端需要处理每个HTTP请求的响应。特别的是,客户端需要在收到401状态码的时候可能需要进行验证;服务端可能会通过3xx的状态码来将客户端进行重定向(但是客户端不要求遵守这些)等。否则,遵循下面的步骤。
  2. 如果客户端收到的响应缺少一个Upgradeheader字段或者Upgradeheader字段包含一个不是"websocket"的值(该值不区分大小写),那么客户端必须关闭连接。
  3. 如果客户端收到的响应缺少一个Connectionheader字段或者Connectionheader字段不包含"Upgrade"的值(该值不区分大小写),那么客户端必须关闭连接。
  4. 如果客户端收到的Sec-WebSocket-Acceptheader字段或者Sec-WebSocket-Acceptheader字段不等于通过Sec-WebSocket-Key字段的值(作为一个字符串,而不是base64解码后)和"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"串联起来,忽略所有前后空格进行base64 SHA-1编码的值,那么客户端必须关闭连接。
  5. 如果客户端收到的响应包含一个Sec-WebSocket-Extensionsheader字段,并且这个字段使用的extension值在客户端的握手请求里面不存在(即服务端使用了一个客户端请求中不存在的值),那么客户端必须关闭连接。(解析这个header字段来确定使用哪个扩展在9.1节中有讨论。)
  6. 如果客户端收到的响应包含一个Sec-WebSocket-Protocolheader字段,并且这个字段包含了一个没有在客户端握手中出现的子协议(即服务端使用了一个客户端请求中子协议字段不存在的值),那么客户端必须关闭连接。

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

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

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

4.2 服务端要求

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

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

4.2.1 解析客户端的握手协议

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

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

  1. 一个HTTP/1.1或者跟高版本的GET请求,包含一个在第三章定义的应该被解析为资源名称(resource name)"Request-URI"字段(或者包含资源名称(resource name)的HTTP/HTTPS绝对路径)。
  2. 包含服务端权限的Hostheader字段。
  3. 不区分大小写的值为"websocket"的Upgradeheader字段。
  4. 不区分大小写的值为"Upgrade"的Connectionheader字段。
  5. 值为base64编码(见[RFC4648的第四章][12])后长度为16字节的Sec-WebSocket-Keyheader字段。
  6. 值为13的Sec-WebSocket-Versionheader值。
  7. 可选的Originheader字段。所有的浏览器都会发送这个字段。缺少此字段的连接不应该认为是来自浏览器。
  8. 可选的Sec-WebSocket-Protocolheader字段,对应的值为客户端支持的子协议,根据权重进行排序。
  9. 可选的Sec-WebSocket-Extensionsheader字段,对应的值为客户端可以使用的扩展。这个字段具体内容会在第9.1节再进行讨论。
  10. 可选的其他字段,如使用cookie或者服务器请求认证的字段。不识别的header字段会依据[RFC2616][13]中内容被忽略。

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

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

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

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

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

  4. 构造以下信息:

    源(origin

    Originheader字段在客户端的握手请求中表示建立连接的脚本属于哪一个源。这个源信息被序列化为ASCII,并且转换为小写。服务端可以使用这个信息来作为判断是否接受这个链接的部分参考内容。如果服务端没有过滤源,那么他会接受任意源的连接。如果服务端没有接受这个连接,那么它必须返回一个对应的HTTP错误码(如403 Forbidden)并且终端这一节描述的WebSocket握手过程。更多详情可以阅读第十章。 关键值(keySec-WebSocket-Keyheader字段在客户端的握手请求中表示一个长度为16字节的base64编码的值。这个编码后的值是用于服务端握手的创建过程,用来表示接受了这个连接。服务端没有必要对Sec-WebSocket-Key值进行解码。 版本(versionSec-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][16]。 扩展(extensions) 表示一个服务端准备使用的协议级扩展列表(可能为空)。如果服务端支持多种扩展,那么这个值必须是客户端握手中已有的数值,是从Sec-WebSocket-Extensions字段中取一到多个值。该字段不存在时则表示此值为空。空字符串与空值不同。客户端没有列举的扩展静止被 使用。应该选择哪些值和如何进行解析可以见9.1节。

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

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

    2. 像[RFC2616][18]中说明的一样,值为"websocket"的Upgradeheader字段。

    3. 值为"Upgrade"的Connectionheader字段。

    4. 一个Sec-WebSocket-Acceptheader字段。这个值由第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节][19]定义的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

数据帧(协议正文)

5.1 概览

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

基础的数据帧协议使用操作码、有效负载长度和在“有效负载数据”中定义的放置“扩展数据”与“引用数据”的指定位置来定义帧类型。特定的bit位和操作码为将来的协议扩展做了保留。

一个数据帧可以在开始握手完成之后和终端发送了一个关闭帧之前的任意一个时间通过客户端或者服务端进行传输(第5.5.1节)。

5.2 基础帧协议

在这节中的这种数据传输部分的有线格式是通过ABNF[RFC5234][6]来进行详细说明的。(注意:不像这篇文档中的其他章节内容,在这节中的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 dataApplication data的数据进行添加掩码。

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

掩码不影响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节中详细讨论。下面是一些符合预期的扩展用法。下面的列表不完整,也不是规范中内容。

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

6 与接收消息(协议正文)

6.1 发送数据

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

  1. 终端必须保证 WebSocket 连接处于 OPEN 状态(见第 4.1 节和第 4.2.2 节)。如果 WebSocket 连接的任意一端的状态发生了改变,终端必须中止以下步骤。
  2. 终端必须将数据按照第 5.2 节定义的 WebSocket 帧进行封装。如果需要发送的数据过大或者在终端希望开始发消息时,如果数据在整体性这一点上不可用,那么终端可能会选择通过在第 5.4 节中定义的一系列帧来进行封装。
  3. 包含数据的第一帧操作码(帧操作码)必须根据第 5.2 节中的内容设置的合适的值,以便接收者将数据解析为文本或者二进制数据。
  4. 最后一个包含数据的帧的 FIN ( FIN 帧)字段必须和第 5.2 节中定义的一样设置为 1 。
  5. 如果数据被发送到了客户端,数据帧必须和第 5.3 节中定义的一样添加掩码。
  6. 如果在 WebsSocket 连接中有协商扩展(第 9 章),在这些扩展中的定义和注意事项也许要额外考虑。
  7. 被格式化的帧必须通过底层的网络连接进行传输。

6.2 接收数据

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

扩展(第 9 章)可能改变数据如何理解的方式,具体包括消息的内容边界。扩展,除了在“应用数据”之前添加“扩展数据”之外,也可以修改“应用数据”(例如压缩它)。

像第 5.3 节中说的那样,服务端在收到客户端的数据帧时必须去除掩码。

关闭连接(协议正文)

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 秒之间的随机值是一个合理的初始延时,但是客户端可以根据自己的经验和特定的应用来选择不同长度的时间延时。

如果第一次重试连接失败,接下来的连接的延时应该变大,使用如截断二进制指数退避方法(译者注:解决以太网碰撞算法,见[截断二进制质数退避算法][8])等来进行设置这个延时。

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 应用之前的协议上。这些状态码在这篇文档中没有进行解释。

8 错误处理(协议正文)

8.1 处理 UTF-8 数据错误

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

9 扩展(协议正文)

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 已知扩展

扩展为实现方式提供了一个机制,即选择使用附加功能协议。这个文档中不定义任何扩展,但是实现跨越使用单独定义的扩展。

10 安全性考虑(协议正文)

这一章描述了一些 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][11])讨论了什么是强 TLS 算法。[RFC5246][12] 的[附录 A.5][13]和[附录 D.3][14]提供了另外的指导。

10.7 处理无用数据

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

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

10.8 在 WebSocket 握手中使用 SHA-1

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

11 IANA 注意事项(协议正文)

11.1 注册新 URI 协议

11.1.1 注册 “ws” 协议

ws URI 定义了 WebSocket 服务器和资源名称。

URI 协议名称

ws

状态

永久

URI 协议语法

使用 ABNF ([RFC5234][12])语法和来自 URI 规范 [RFC3986][13] 的 ABNF 终端:

"ws:" "//" authority path-abempty [ "?" query ]

path-abemptyquery [RFC3986][14] 部分组成了发送给服务端的资源名称,来标记需要的服务类型。其他的部分在 [RFC3986][15] 中定义了含义。

URI 协议含义

这个方案的唯一操作就是使用 WebSocket 协议打开一个连接。

编码注意事项

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

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

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

WebSocket Protocol

互操作性注意事项

使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。

安全性注意事项

见”安全性注意事项”一节。

联系

HYBI WG <hybi@ietf.org\\\>

作者/更改控制者

IETF <iesg@ietf.org\\\>

关联

[RFC 6455][20]

11.1.2 注册 “wss”协议

一个 wss 的 URI 定义了一个 WebSocket 服务器和资源名称,表明通过这个链接的传输需要通过 TLS(包含标准的 TLS 能力例如数据保密性和完整性以及终端认证)来进行保护。

URI 协议名称

wss

状态

永久

URI 协议语法

使用 ABNF ([RFC5234][21])语法和来自 URI 规范 [RFC3986][22] 的 ABNF 终端:

"wss:" "//" authority path-abempty [ "?" query ]

path-abemptyquery [RFC3986][23] 部分组成了发送给服务端的资源名称,来标记需要的服务类型。其他的部分在 [RFC3986][24] 中定义了含义。

URI 协议含义

这个方案的唯一操作就是使用 WebSocket 协议打开一个连接,通过 TLS 加密。

编码注意事项

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

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

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

WebSocket Protocol

互操作性注意事项

使用 WebSocket 时需要使用 1.1 或者更高版本的 HTTP 协议。

安全性注意事项

见”安全性注意事项”一节。

联系

HYBI WG <hybi@ietf.org\\\>

作者/更改控制者

IETF <iesg@ietf.org\\\>

关联

[RFC 6455][29]

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

这一节描述一个在 HTTP 升级凭证注册的关键值,在 [RFC2817][30] 定义。

凭证名称

WebSocket

作者/更改控制者

IETF <iesg@ietf.org\\\>

关联

[RFC 6455][31]

11.3 注册新的 HTTP 头字段

Sec-WebSocket-Key

这一节描述一个注册在永久消息头字段名称中的头字段,在 [RFC3864][32] 定义。

头字段名称

Sec-WebSocket-Key

应用协议

http

状态

标准

作者/更改控制者

IETF

说明文档

[RFC 6455][33]

关联信息

这个头字段只用于 WebSocket 开始握手。

Sec-WebSocket-Key 头字段是用在 WebSocket 开始握手阶段。它是通过客户端发送给服务端,这部分信息用于服务端证明收到一个有效的 WebSocket 握手操作的认证。这可以帮助确认服务端不会接收可能被用来向 WebSocket 服务任意发送数据的非 WebSocket 客户端的连接(例如 HTTP 客户端)。

Sec-WebSocket-Key 头字段禁止在一个 HTTP 请求中出现多次。

11.3.2 Sec-WebSocket-Extensions

这一节描述一个注册在永久消息头字段名称中的头字段,在 [RFC3864][34] 定义。

头字段名称

Sec-WebSocket-Extensions

应用协议

http

状态

标准

作者/更改控制者

IETF

说明文档

[RFC 6455][35]

关联信息

这个头字段只用于 WebSocket 开始握手。

Sec-WebSocket-Extensions 头字段是用于 WebSocket 开始握手阶段。它最开始是通过客户端发送给服务端,然后通过服务端发送给客户端,来对一个在连接中的协议级的扩展进行协商。

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

11.3.3 Sec-WebSocket-Accept

这一节描述一个注册在永久消息头字段名称中的头字段,在 [RFC3864][36] 定义。

头字段名称

Sec-WebSocket-Accept

应用协议

http

状态

标准

作者/更改控制者

IETF

说明文档

[RFC 6455][37]

关联信息

这个头字段只用于 WebSocket 开始握手。

Sec-WebSocket-Accpet 头字段是用于 WebSocket 开始握手阶段。它是通过服务端发送给客户端,用来确认服务端会初始化一个 WebSocket 连接。

Sec-WebSocket-Accpet 头在一个 HTTP 响应中不允许出现超过1次。

11.3.4 Sec-WebSocket-Protocol

这一节描述一个注册在永久消息头字段名称中的头字段,在 [RFC3864][38] 定义。

头字段名称

Sec-WebSocket-Protocol

应用协议

http

状态

标准

作者/更改控制者

IETF

说明文档

[RFC 6455][39]

关联信息

这个头字段只用于 WebSocket 开始握手。

Sec-WebSocket-Protocol 头字段是用于 WebSocket 开始握手阶段。它是从客户端发送给服务端,然后从服务端返回给服务端来确认连接的子协议。这个机制能够让双方选择一个子协议,同时向服务端确认可以支持这个子协议。

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

11.3.5 Sec-WebSocket-Version

这一节描述一个注册在永久消息头字段名称中的头字段,在 [RFC3864][40] 定义。

头字段名称

Sec-WebSocket-Version

应用协议

http

状态

标准

作者/更改控制者

IETF

说明文档

[RFC 6455][41]

关联信息

这个头字段只用于 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][42]中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。

作为此注册表的一部分,IANA 包含了一下信息:

扩展定义

这个扩展的定义,将在 Sec-WebSocket-Extensions 头字段中使用,在此规范的第 11.3.2 节注册。这个值必须满足在此规范第 9.1 节中定义的扩展凭证要求。

扩展通用名

扩展名称,一般称为扩展名。

扩展定义

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

已知不兼容扩展

已知的不兼容的扩展定义列表。

WebSocket 扩展名是受到“先到先得” IANA 注册政策 [RFC5226][43] 限制的。

这个注册表里没有初始值。

11.5 WebSocket 子协议名注册表

这个规范根据[RFC5526][44]中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 扩展名称。

作为此注册表的一部分,IANA 包含了一下信息:

子协议定义

子协议的标识符将在 Sec-WebSocket-Protocol 头字段中使用,在此规范的第 11.3.4 节中注册。这个之必须符合此规范第 4.1 节中的第 10 项要求—换句话说,这个之必须是 [RFC2616][45] 中定义的凭证。

子协议通用名

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

子协议定义

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

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

11.6 WebSocket 版本号注册表

该规范根据 [RFC5226][47] 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 版本号。

作为此注册表的一部分,IANA 包含了一下信息:

版本号

版本号是用于在此规范第 4.1 节中制定的 Sec-WebSocket-Version 字段。这个值必须是一个范围在 0 到 255(含)之间的非负整数。

参考

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

状态

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

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

“标准”的版本号被记录在 RFC 文档中,被认为是一个重大、稳定的 WebSocket 协议版本,例如定义在这个 RFC 中的版本。“标准”版本号是受到 “IETF 评论” IANA 注册政策 [RFC5526][48] 限制的。

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

IANA 已经向注册表中添加了如下的初始值。

版本号 引用 状态
0 [draft-ietf-hybi-thewebsocketprotocol-00][50] 临时
1 [draft-ietf-hybi-thewebsocketprotocol-01][51] 临时
2 [draft-ietf-hybi-thewebsocketprotocol-02][52] 临时
3 [draft-ietf-hybi-thewebsocketprotocol-03][53] 临时
4 [draft-ietf-hybi-thewebsocketprotocol-04][54] 临时
5 [draft-ietf-hybi-thewebsocketprotocol-05][55] 临时
6 [draft-ietf-hybi-thewebsocketprotocol-06][56] 临时
7 [draft-ietf-hybi-thewebsocketprotocol-07][57] 临时
8 [draft-ietf-hybi-thewebsocketprotocol-08][58] 临时
9 保留
10 保留
11 保留
12 保留
13 [RFC6455][59]

11.7 WebSocket 关闭码注册表

该规范根据 [RFC5226][60] 中规定的原则为 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.org [RFC6455][61]
1001 离开 hybi@ietf.org [RFC6455][62]
1002 协议错误 hybi@ietf.org [RFC6455][63]
1003 不支持的数据类型 hybi@ietf.org [RFC6455][64]
1004 保留 hybi@ietf.org [RFC6455][65]
1005 没有收到状态码 hybi@ietf.org [RFC6455][66]
1006 异常关闭 hybi@ietf.org [RFC6455][67]
1007 无效的帧数据 hybi@ietf.org [RFC6455][68]
1008 违反政策 hybi@ietf.org [RFC6455][69]
1009 消息太大 hybi@ietf.org [RFC6455][70]
1010 强制扩展 hybi@ietf.org [RFC6455][71]
1011 内部服务器错误 hybi@ietf.org [RFC6455][72]
1015 TLS握手 hybi@ietf.org [RFC6455][73]

11.8 WebSocket 操作码注册表

该规范根据 [RFC5226][74] 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 操作码。

作为此注册表的一部分,IANA 包含了一下信息:

操作码

操作码表示定义在第 5.2 节中的 WebSocket 帧的帧类型。操作码是一个范围在 0 到 15(含)的数字。

含义

操作码的含义。

关联

请求操作码的规范。

WebSocket 操作码是受到“标准行为”IANA 注册表政策 [RFC5266][75] 限制的。

IANA 已经向注册表中注册了一下初始值。

操作码 含义 关联
0 连续帧 [RFC6455][76]
1 文本帧 [RFC6455][77]
2 二进制帧 [RFC6455][78]
8 连接关闭帧 [RFC6455][79]
9 心跳 Ping 帧 [RFC6455][80]
10 心跳 Pong 帧 [RFC6455][81]

11.9 WebSocket 帧头 bit 字段注册表

该规范根据 [RFC5226][82] 中规定的原则为 WebSocket 协议创建了一个新的 IANA 注册表,用于 WebSocket 帧头 bit 字段。这个注册表控制分配的 bit 位为第 5.2 节中的 RSV1、RSV2 和 RSV3。

这些 bit 位是保留给将来的版本或者文档中的扩展。

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

12 使用其他规范中的WebSocket协议(协议正文)

WebSocket协议旨在由另一规范使用,以提供动态作者定义内容的通用机制。例如,在定义脚本 API 的规范中定义 WebSocket 协议。

例如一个规范首先需要建立 WebSocket 连接,提供该算法:

  • 目标资源,包含一个主机名(host)和一个端口(port)
  • 资源名称,允许在一个主机和端口上识别多个服务。
  • 安全标记,当这个值为 true 时,连接应该被加密,如果为 false 时则不需要。
  • 原始[RFC6454][13]的ASCII序列化,负责连接。
  • 可选的,基于 WebSocket 连接的通过一个字符串定义的协议。

主机端口资源名称安全标记通常是使用解析 WebSocket URI 组件,通过 URI 来获取。如果 URI 中没有指定这些 WebSocket 字段,那么这个解析将失败。

如果在任意时间连接被关闭了,那么规范需要使用关闭 WebSocket 连接算法(第 7.1.1 节)。

第 7.1.4 节定义了什么时候WebSocket 连接关闭

当连接打开时,文档需要处理收到一条 WebSocket 消息(第 6.2 节)的场景。

为了向已经建立的连接发送一些数据,文档需要处理发送 WebSocket 消息(第 6.1 节)。

13. Acknowledgements

Special thanks are due to Ian Hickson, who was the original author and editor of this protocol. The initial design of this specification benefitted from the participation of many people in the WHATWG and WHATWG mailing list. Contributions to that specification are not tracked by section, but a list of all who contributed to that specification is given in the WHATWG HTML specification at http://whatwg.org/html5.

Special thanks also to John Tamplin for providing a significant amount of text for the "Data Framing" section of this specification.

Special thanks also to Adam Barth for providing a significant amount of text and background research for the "Data Masking" section of this specification.

Special thanks to Lisa Dusseault for the Apps Area review (and for helping to start this work), Richard Barnes for the Gen-Art review, and Magnus Westerlund for the Transport Area Review. Special thanks to HYBI WG past and present WG chairs who tirelessly worked behind the scene to move this work toward completion: Joe Hildebrand, Salvatore Loreto, and Gabriel Montenegro. And last but not least, special thank you to the responsible Area Director Peter Saint-Andre.

Thank you to the following people who participated in discussions on the HYBI WG mailing list and contributed ideas and/or provided detailed reviews (the list is likely to be incomplete): 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, and Alexander Philippou. Note that people listed above didn't necessarily endorse the end result of this work.

14. References

14.1. Normative References

[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, "Hypertext Transfer 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. Informative References

[RFC4122] Leach, P., Mealling, M., and R. Salz, "A Universally Unique IDentifier (UUID) URN Namespace", RFC 4122, 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/>.

Authors' Addresses

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!