首页 > 其他分享 >RFC 6455-websocket协议 -- 中文翻译

RFC 6455-websocket协议 -- 中文翻译

时间:2024-06-05 12:04:28浏览次数:29  
标签:websocket Sec -- 握手 WebSocket 服务器 中文翻译 连接 客户端

英文文档的在源地址:RFC 6455 - The WebSocket Protocol

中文翻译如下:(未格式化整理)

///

互联网工程任务组(IETF)

请求评论:6455

作者:I. Fette

公司:Google, Inc.

类别:标准轨道

ISSN:2070-1721

A. Melnikov

公司:Isode Ltd.

2011年12月

WebSocket协议

摘要

WebSocket协议允许在受控环境中运行不受信任代码的客户端与已选择接收来自该代码通信的远程主机之间进行双向通信。用于此的安全模型是基于起源的安全模型,通常被网页浏览器所使用。该协议包括一个打开握手过程,随后是基于TCP的基本消息帧。这项技术的目标是提供一种机制,用于需要与服务器进行双向通信的基于浏览器的应用程序,这种机制不依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或<iframe>和长轮询)。

本备忘录的状态

这是一份互联网标准轨道文档。

本文档是互联网工程任务组(IETF)的产品。它代表了IETF社区的共识。它已经接受公众审查,并获得了互联网工程指导组(IESG)的批准以进行发布。关于互联网标准的更多信息,请参阅RFC 5741的第2节。

关于本文档当前的状态、任何勘误表以及如何对本文档提供反馈的信息,可访问http://www.rfc-editor.org/info/rfc6455获取。

版权声明

版权所有(c)2011年IETF信托基金和本文档中标识为文档作者的个人。保留所有权利。

本文档受BCP 78和IETF信托基金关于IETF文档的法律条款(http://trustee.ietf.org/license-info)的约束,这些条款在本文档发布之日生效。请仔细阅读这些文档,因为它们描述了您对本文档的权利和限制。从本文档中提取的代码组件必须包含简化版BSD许可证文本,如《IETF信托基金法律条款》第4.e节所述,并且不提供任何担保,如简化版BSD许可证所述。

目录

1.引言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.1. 背景 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.2. 协议概述 . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.3. 打开握手 . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.4. 关闭握手 . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.5. 设计理念 . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.6. 安全模型 . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.7. 与TCP和HTTP的关系 . . . . . . . . . . . . . . . . . . . . . 11

1.8. 建立连接 . . . . . . . . . . . . . . . . . . . . . . . . . . 11

1.9. 使用WebSocket协议的子协议 . . . . . . . . . . . . . . . . . 12

2.符合性要求 . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.1. 术语和其他约定 . . . . . . . . . . . . . . . . . . . . . . . 13

3.WebSocket URI . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.打开握手 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.1. 客户端要求 . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.2. 服务器端要求 . . . . . . . . . . . . . . . . . . . . . . . . 20

4.2.1. 读取客户端的打开握手 . . . . . . . . . . . . . . . . . 21

4.2.2. 发送服务器的打开握手 . . . . . . . . . . . . . . . . . 22

4.3. 握手过程中使用的新头部字段的收集ABNF(增强巴科斯-诺尔范式) 25

4.4. 支持WebSocket协议的多个版本 . . . . . . . . . . . . . . . . 26

5.数据帧格式 . . . . . . . . . . . . . . . . . . . . . . . . . 27

5.1. 概述 . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

5.2. 基础帧协议 . . . . . . . . . . . . . . . . . . . . . . . . 28

5.3. 客户端到服务器的掩码 . . . . . . . . . . . . . . . . . . 32

5.4. 分片 . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

5.5. 控制帧 . . . . . . . . . . . . . . . . . . . . . . . . . . 36

5.5.1. 关闭 . . . . . . . . . . . . . . . . . . . . . . . . . 36

5.5.2. Ping . . . . . . . . . . . . . . . . . . . . . . . . . 37

5.5.3. Pong . . . . . . . . . . . . . . . . . . . . . . . . . 37

5.6. 数据帧 . . . . . . . . . . . . . . . . . . . . . . . . . . 38

5.7. 示例 . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

5.8. 可扩展性 . . . . . . . . . . . . . . . . . . . . . . . . . 39

6.发送和接收数据 . . . . . . . . . . . . . . . . . . . . . . . . 39

6.1. 发送数据 . . . . . . . . . . . . . . . . . . . . . . . . . 39

6.2. 接收数据 . . . . . . . . . . . . . . . . . . . . . . . . . 40

7.关闭连接 . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

7.1. 定义 . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

7.1.1. 关闭WebSocket连接 . . . . . . . . . . . . . . . . . . 41

7.1.2. 启动WebSocket关闭握手 . . . . . . . . . . . . . . . 42

7.1.3. WebSocket关闭握手已开始 . . . . . . . . . . . . . . 42

7.1.4. WebSocket连接已关闭 . . . . . . . . . . . . . . . . . 42

7.1.5. WebSocket连接关闭代码 . . . . . . . . . . . . . . . . 42

7.1.6. WebSocket连接关闭原因 . . . . . . . . . . . . . . . 43

7.1.7. WebSocket连接失败 . . . . . . . . . . . . . . . . . . 43

7.2. 异常关闭 . . . . . . . . . . . . . . . . . . . . . . . . 44

7.2.1. 客户端发起的关闭 . . . . . . . . . . . . . . . . . 44

7.2.2. 服务器端发起的关闭 . . . . . . . . . . . . . . . . 44

7.2.3. 从异常关闭中恢复 . . . . . . . . . . . . . . . . . 44

7.3. 连接的正常关闭 . . . . . . . . . . . . . . . . . . . . 45

7.4. 状态码 . . . . . . . . . . . . . . . . . . . . . . . . 45

7.4.1. 已定义的状态码 . . . . . . . . . . . . . . . . . 45

7.4.2. 保留的状态码范围 . . . . . . . . . . . . . . . . 47

8.错误处理 . . . . . . . . . . . . . . . . . . . . . . . . . 48

8.1. 处理UTF-8编码数据中的错误 . . . . . . . . . . . . . . 48

9.扩展 . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

9.1. 协商扩展 . . . . . . . . . . . . . . . . . . . . . . . 48

9.2. 已知扩展 . . . . . . . . . . . . . . . . . . . . . . 50

10.安全性考虑 . . . . . . . . . . . . . . . . . . . . . . . . 50

10.1. 非浏览器客户端 . . . . . . . . . . . . . . . . . . . 50

10.2. 原始来源的考虑 . . . . . . . . . . . . . . . . . . . 50

10.3. 对基础设施的攻击(掩码) . . . . . . . . . . . . . . 51

10.4. 与具体实现相关的限制 . . . . . . . . . . . . . . . . 52

10.5. WebSocket客户端认证 . . . . . . . . . . . . . . . . . 53

10.6. 连接的保密性和完整性 . . . . . . . . . . . . . . . . 53

10.7. 无效数据的处理 . . . . . . . . . . . . . . . . . . . 53

10.8. WebSocket握手对SHA-1的使用 . . . . . . . . . . . . . 54

11.IANA(互联网号码分配机构)的考虑 . . . . . . . . . . . . . . . . . 54

11.1. 注册新的URI方案 . . . . . . . . . . . . . . . . . . . . 54

11.1.1. 注册“ws”方案 . . . . . . . . . . . . . . . . . . . 54

11.1.2. 注册“wss”方案 . . . . . . . . . . . . . . . . . . . 55

11.2. 注册“WebSocket”HTTP升级关键字 . . . . . . . . . . . 56

11.3. 注册新的HTTP头部字段 . . . . . . . . . . . . . . . . . 57

11.3.1. Sec-WebSocket-Key . . . . . . . . . . . . . . . . . . 57

11.3.2. Sec-WebSocket-Extensions . . . . . . . . . . . . . . . 58

11.3.3. Sec-WebSocket-Accept . . . . . . . . . . . . . . . . . 58

11.3.4. Sec-WebSocket-Protocol . . . . . . . . . . . . . . . . 59

11.3.5. Sec-WebSocket-Version . . . . . . . . . . . . . . . . 60

11.4. WebSocket扩展名称注册表 . . . . . . . . . . . . . . . . 61

11.5. WebSocket子协议名称注册表 . . . . . . . . . . . . . . 61

11.6. WebSocket版本号注册表 . . . . . . . . . . . . . . . . 62

11.7. WebSocket关闭代码编号注册表 . . . . . . . . . . . . . 64

11.8. WebSocket操作码注册表 . . . . . . . . . . . . . . . . 65

11.9. WebSocket帧头位注册表 . . . . . . . . . . . . . . . . 66

12.从其他规范中使用WebSocket协议 . . . . . . . . . . . . . . 66

13.致谢 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

14.参考文献 . . . . . . . . . . . . . . . . . . . . . . . . . . 68

14.1. 规范性引用 . . . . . . . . . . . . . . . . . . . . . . . 68

14.2. 资料性引用 . . . . . . . . . . . . . . . . . . . . . . . 69

引言

1.1. 背景

本节为非规范性内容。

历史上,要创建需要在客户端和服务器之间双向通信的Web应用程序(例如,即时消息传递和游戏应用程序),需要滥用HTTP来轮询服务器以获取更新,同时将上游通知作为单独的HTTP调用发送[RFC6202]。

这导致了一系列问题:

o 服务器被迫为每个客户端使用多个不同的底层TCP连接:一个用于向客户端发送信息,每个传入的消息都使用一个新的连接。

o 线路协议开销很高,每个客户端到服务器的消息都带有HTTP头部。

o 客户端脚本被迫维护从传出连接到传入连接的映射以跟踪回复。

一个更简单的解决方案是使用单个TCP连接进行双向流量。这就是WebSocket协议提供的。结合WebSocket API [WSAPI],它为从网页到远程服务器的双向通信提供了一种替代HTTP轮询的方法。

同样的技术可以用于各种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)格式。请求行和状态行的产生方式在[RFC2616]中定义。

在两种情况下,首行后面都跟着一组无序的头部字段。这些头部字段的意义在本文档的第4节中说明。此外,可能还存在其他头部字段,例如cookies [RFC6265]。头部字段的格式和解析方式按照[RFC2616]中定义的方式执行。

一旦客户端和服务器都发送了握手请求,并且握手成功,那么数据传输部分就开始了。这是一个双向通信通道,每一方都可以独立地随意发送数据。

在成功握手之后,客户端和服务器通过被称为“消息”的概念单元来回传输数据。在物理线路上,一个消息由一个或多个帧组成。WebSocket消息并不一定对应于特定的网络层帧结构,因为中间设备可能会对碎片化的消息进行合并或拆分。

帧有一个与之关联的类型。属于同一消息的每个帧都包含相同类型的数据。大致来说,帧的类型包括文本数据(被解释为UTF-8 [RFC3629] 文本)、二进制数据(其解释由应用程序决定)和控制帧(不用于携带应用程序数据,而是用于协议级别的信号,比如通知连接应该被关闭)。这个版本的协议定义了六种帧类型,并为将来的使用保留了十种。

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],握手中的头部字段可以由客户端以任何顺序发送,因此不同头部字段的接收顺序并不重要。

GET方法的“Request-URI”[RFC2616]用于标识WebSocket连接的端点,既允许从一个IP地址服务多个域,也允许单个服务器服务多个WebSocket端点。

客户端在其握手的|Host|头部字段中包含主机名,按照[RFC2616]的规定,这样客户端和服务器都可以验证它们对正在使用的主机是否达成一致。

额外的头部字段用于在WebSocket协议中选择选项。这个版本中典型的选项包括子协议选择器(|Sec-WebSocket-Protocol|)、客户端支持的扩展列表(|Sec-WebSocket-Extensions|)、|Origin|头部字段等。|Sec-WebSocket-Protocol|请求头部字段可用于指示客户端接受哪些子协议(在WebSocket协议之上分层的应用级协议)。服务器选择一个或零个可接受的协议,并在其握手过程中回显该值,以指示已选择该协议。

Sec-WebSocket-Protocol: chat

[RFC6454]中定义的|Origin|头部字段用于防止浏览器中的脚本使用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"进行连接,这个GUID不太可能被不理解WebSocket协议的网络端点使用。然后,对这个连接结果进行SHA-1哈希(160位)[FIPS.180-3],并进行base64编码(参见[RFC4648]的第4节),并将此哈希值返回给服务器握手。

具体来说,如果像上面示例中的那样,|Sec-WebSocket-Key|头部字段的值是"dGhlIHNhbXBsZSBub25jZQ==",服务器会将字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"与该值连接起来,形成字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。然后,服务器会对这个字符串进行SHA-1哈希计算,得到的值是0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。这个值随后会被base64编码(参见[RFC4648]的第4节),得到值"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="。这个值随后会在|Sec-WebSocket-Accept|头部字段中回显。

服务器的握手过程比客户端的握手过程简单得多。第一行是一个HTTP状态行,状态码为101:

HTTP/1.1 101 Switching Protocols

除了101以外的任何状态码都表示WebSocket握手没有完成,并且仍然适用HTTP的语义。状态码之后是头部字段。

|Connection|和|Upgrade|头部字段完成了HTTP升级。|Sec-WebSocket-Accept|头部字段指示服务器是否愿意接受连接。如果存在这个字段,它必须包含客户端在|Sec-WebSocket-Key|中发送的nonce的哈希值以及一个预定义的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相关的选项字段来_设置_cookie,如[RFC6265]所述。

1.4. 关闭握手

本节为非规范性内容。

关闭握手比打开握手简单得多。

任何一方都可以发送一个包含特定控制序列的数据的控制帧来开始关闭握手(详见第5.5.1节)。在接收到这样的帧后,如果尚未发送关闭帧,另一方会发送一个关闭帧作为响应。在接收到_那个_控制帧后,第一个对等方随后关闭连接,并确信不会再有数据到来。

在发送一个指示连接应被关闭的控制帧后,对等方不会发送任何进一步的数据;在接收到一个指示连接应被关闭的控制帧后,对等方会丢弃接收到的任何进一步的数据。

对于两个对等方来说,同时发起这个握手是安全的。

关闭握手的目的是对TCP关闭握手(FIN/ACK)进行补充,因为TCP关闭握手在端到端上并不总是可靠的,特别是在存在拦截代理和其他中间设备的情况下。

通过发送一个关闭帧并等待关闭帧的响应,可以避免某些可能导致数据不必要丢失的情况。例如,在某些平台上,如果带有数据的接收队列的套接字被关闭,将发送一个RST数据包,这将导致收到RST的一方调用recv()失败,即使还有等待读取的数据。

1.5. 设计理念

本节为非规范性内容。

WebSocket协议的设计基于这样一个原则:应该尽量减少帧的封装(唯一存在的封装是为了使协议基于帧而不是基于流,并支持区分Unicode文本帧和二进制帧)。预计应用程序层会在WebSocket之上添加元数据,这与应用程序层在TCP之上添加元数据的方式相同(例如,HTTP)。

从概念上讲,WebSocket实际上只是TCP之上的一层,它做了以下几件事:

o 为浏览器添加了一个基于Web来源的安全模型

o 添加了一个寻址和协议命名机制,以支持单个端口上的多个服务和单个IP地址上的多个主机名

o 在TCP之上封装了一个帧机制,以回到TCP所基于的IP包机制,但不受长度限制

o 包含一个额外的带内关闭握手,旨在存在代理和其他中间设备的情况下工作

除此之外,WebSocket没有添加任何其他功能。基本上,它旨在尽可能接近直接将原始TCP暴露给脚本,同时考虑到Web的约束。WebSocket的设计还使得其服务器可以与HTTP服务器共享端口,通过将其握手作为有效的HTTP Upgrade请求来实现。从概念上讲,可以使用其他协议来建立客户端-服务器消息传递,但WebSocket的意图是提供一个相对简单的协议,该协议可以与HTTP和已部署的HTTP基础设施(如代理)共存,并且考虑到安全性,该协议尽可能接近TCP,同时有针对性地添加了一些功能以简化使用和保持简单性(如添加消息语义)。

该协议旨在具有可扩展性;未来版本可能会引入诸如多路复用等额外概念。

1.6. 安全模型

本节为非规范性内容。

WebSocket协议使用Web浏览器使用的源模型来限制哪些网页可以在使用WebSocket协议时与WebSocket服务器建立联系。当然,当WebSocket协议由专用客户端直接使用(即不是通过Web浏览器从网页使用)时,源模型并不适用,因为客户端可以提供任何任意的源字符串。

该协议旨在无法与现有协议(如SMTP [RFC5321]和HTTP)的服务器建立连接,同时允许HTTP服务器选择性地支持该协议(如果需要)。这是通过执行严格且详尽的握手过程以及限制在握手完成之前可以插入连接的数据(从而限制对服务器的影响程度)来实现的。

当其他协议(尤其是HTTP)的数据发送到WebSocket服务器时,比如,如果HTML“表单”被提交到WebSocket服务器,WebSocket也意图无法建立连接。这主要通过要求服务器证明它读取了握手过程来实现,而服务器只有在握手包含适当部分时才能证明,这些部分只能由WebSocket客户端发送。特别是,在编写本规范时,以“|Sec-|”开头的字段无法仅通过HTML和JavaScript API(如XMLHttpRequest [XMLHttpRequest])从Web浏览器被攻击者设置。

1.7. 与TCP和HTTP的关系

本节为非规范性内容。

WebSocket协议是一个基于TCP的独立协议。它与HTTP的唯一关系是,它的握手过程被HTTP服务器解释为Upgrade请求。

默认情况下,WebSocket协议使用端口80进行常规WebSocket连接,使用端口443进行通过传输层安全性(TLS)[RFC2818]隧道传输的WebSocket连接。

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子协议,由网络上的许多服务器实现,他们可以将其命名为“chat.example.com”。如果Example组织将其竞争子协议命名为“chat.example.org”,那么两个子协议可以由服务器同时实现,服务器根据客户端发送的值动态选择使用哪个子协议。

子协议可以通过更改子协议名称(例如,从“bookings.example.net”更改为“v2.bookings.example.net”)以向后不兼容的方式进行版本控制。这些子协议将被WebSocket客户端视为完全独立的。可以通过重复使用相同的子协议字符串并仔细设计实际子协议以支持这种可扩展性来实现向后兼容的版本控制。

  1. 一致性要求

本规范中的所有图表、示例和注释都是非规范性的,所有明确标记为非规范性的部分也是如此。本规范中的其他所有内容都是规范性的。

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应如[RFC2119]所述进行解释。

作为算法一部分以祈使句形式表述的要求(如“删除任何前导空格字符”或“返回false并中止这些步骤”)应根据在引入算法时使用的关键词(“MUST”、“SHOULD”、“MAY”等)的含义进行解释。

以算法或具体步骤形式表述的一致性要求可以以任何方式实现,只要最终结果是等效的即可。(特别是,本规范中定义的算法旨在易于遵循,而非追求性能。)

2.1. 术语和其他约定

ASCII 应指[ANSI.X3-4.1986]中定义的字符编码方案。

本文档引用了UTF-8值,并使用STD 63 [RFC3629]中定义的UTF-8表示格式。

像命名算法或定义这样的关键术语用_这种_形式表示。

报头字段或变量的名称用|这种|形式表示。

变量值用/这种/形式表示。

本文档引用了_终止WebSocket连接_的过程。这个过程在7.1.7节中定义。

_将字符串转换为ASCII小写_意味着将U+0041到U+005A(即LATIN CAPITAL LETTER A到LATIN CAPITAL LETTER Z)范围内的所有字符替换为U+0061到U+007A(即LATIN SMALL LETTER A到LATIN SMALL LETTER Z)范围内的对应字符。

以_ASCII不区分大小写_的方式比较两个字符串意味着逐个代码点进行比较,但U+0041到U+005A(即LATIN CAPITAL LETTER A到LATIN CAPITAL LETTER Z)范围内的字符与U+0061到U+007A(即LATIN SMALL LETTER A到LATIN SMALL LETTER Z)范围内的对应字符也被视为匹配。

本文档中使用的“URI”一词的定义与[RFC3986]中的定义相同。

当实现作为WebSocket协议的一部分需要_发送_数据时,实现可以任意延迟实际传输,例如,缓冲数据以发送更少的IP数据包。

请注意,本文档在不同部分使用了[RFC5234]和[RFC2616]两种ABNF变体。

  1. WebSocket URIs

本规范定义了两个URI方案,使用了在RFC 5234 [RFC5234]中定义的ABNF语法,以及由URI规范RFC 3986 [RFC3986]定义的术语和ABNF产生式。

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

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

host = <host, 在[RFC3986]的第3.2.2节中定义>

port = <port, 在[RFC3986]的第3.2.3节中定义>

path = <path-abempty, 在[RFC3986]的第3.3节中定义>

query = <query, 在[RFC3986]的第3.4节中定义>

端口组件是可选的;对于“ws”,默认端口是80,而对于“wss”,默认端口是443。

如果方案组件与“wss”不区分大小写地匹配,则称URI为“安全的”(并说“已设置安全标志”)。

“资源名称”(在第4.1节中也称为/resource name/)可以通过以下方式构造:

  • 如果路径组件为空,则添加一个“/”
  • 路径组件
  • 如果查询组件非空,则添加一个“?”
  • 查询组件

在WebSocket URI的上下文中,片段标识符没有意义,并且不得在这些URI上使用。与任何URI方案一样,当“#”字符不表示片段的开始时,必须转义为%23。

  1. 打开握手

4.1. 客户端要求

为了_建立WebSocket连接_,客户端会打开一个连接并发送一个如本节定义的握手请求。连接被定义为初始时处于CONNECTING(连接中)状态。客户端需要提供/host/(主机)、/port/(端口)、/resource name/(资源名称)和/secure/(安全)标志,这些都是第3节中讨论的WebSocket URI的组成部分,以及要使用的/protocols/(协议)和/extensions/(扩展)列表。此外,如果客户端是Web浏览器,它还需要提供/origin/(源)。

在受控环境中运行的客户端(例如,与特定运营商绑定的手机浏览器)可以将连接管理卸载给网络上的另一个代理。在这种情况下,出于本规范的目的,客户端被认为包括手持设备软件和任何此类代理。

当客户端需要_建立WebSocket连接_时,给定一组(/host/、/port/、/resource name/和/secure/标志),以及要使用的/protocols/和/extensions/列表,如果是Web浏览器的话,还需要提供/origin/。客户端必须打开一个连接,发送一个开手握手,并读取服务器的响应握手。本节将详细说明如何打开连接、在开手握手中应发送什么内容以及如何解释服务器的响应。在以下文本中,我们将使用第3节中定义的术语,如"/host/"和"/secure/标志"。

  1. 传入此算法的WebSocket URI的组件(/host/、/port/、/resource name/和/secure/标志)必须根据第3节中指定的WebSocket URI规范有效。如果任何组件无效,客户端必须_失败WebSocket连接_并中止这些步骤。
  2. 如果客户端已经与由/host/和/port/对标识的远程主机(IP地址)建立了WebSocket连接,即使远程主机使用其他名称也知道,客户端必须等待该连接建立或该连接失败。在CONNECTING状态下,必须没有超过一个连接。如果同时尝试对同一IP地址进行多个连接,客户端必须将它们序列化,以便一次只有一个连接在执行以下步骤。

如果客户端无法确定远程主机的IP地址(例如,因为所有通信都是通过执行DNS查询的代理服务器进行的),则在本步骤中,客户端必须假定每个主机名指的是一个不同的远程主机,但客户端应该限制同时挂起的连接总数至一个合理低的数量(例如,客户端可能允许同时对a.example.com和b.example.com的挂起连接,但如果请求对单个主机的三十个同时连接,这可能不被允许)。例如,在Web浏览器环境中,客户端需要考虑用户打开的标签页数量来设置同时挂起连接数量的限制。

注意:这使得脚本仅通过打开大量WebSocket连接到远程主机来执行拒绝服务攻击变得更加困难。当受到攻击时,服务器可以通过在关闭连接之前暂停来进一步减轻自身的负载,因为这将降低客户端重新连接的速率。

注意:客户端与一个远程主机可以建立的已建立WebSocket连接数量没有限制。当服务器遭受高负载时,可以拒绝接受来自具有过多现有连接的主机/IP地址的连接,或者断开占用大量资源的连接。

  1. 代理使用:如果客户端配置为在使用WebSocket协议连接到主机/host/和端口/port/时使用代理,则客户端应该连接到该代理,并请求它打开到由/host/给出的主机和由/port/给出的端口的TCP连接。

示例:例如,如果客户端对所有流量使用HTTP代理,那么如果它试图连接到example.com服务器的80端口,它可能会向代理服务器发送以下行:

CONNECT example.com:80 HTTP/1.1

Host: example.com

如果有密码,连接可能看起来像这样:

CONNECT example.com:80 HTTP/1.1

Host: example.com

Proxy-authorization: Basic ZWRuYW1vZGU6bm9jYXBlcyE=

如果客户端没有配置为使用代理,则应该直接打开到由/host/给出的主机和由/port/给出的端口的TCP连接。

注意:鼓励不暴露用于选择WebSocket连接代理的明确用户界面的实现(与其他代理分开),如果可用,则使用SOCKS5 [RFC1928]代理进行WebSocket连接,或者在没有SOCKS5代理的情况下,优先使用为HTTPS连接配置的代理,而不是为HTTP连接配置的代理。

对于代理自动配置脚本的目的,必须根据WebSocket URI的定义(如第3节所述)使用/host/、/port/、/resource name/和/secure/标志来构造传递给函数的URI。

注意:WebSocket协议可以在代理自动配置脚本中通过方案(对于非加密连接使用“ws”,对于加密连接使用“wss”)进行识别。

  1. 如果连接无法打开,无论是直接连接失败还是使用的任何代理返回了错误,则客户端必须_终止WebSocket连接_并中止连接尝试。
  2. 如果/secure/为true,则客户端必须在打开连接后并在发送握手数据之前执行TLS握手[RFC2818]。如果这失败了(例如,无法验证服务器的证书),则客户端必须_终止WebSocket连接_并中止连接。否则,该通道上的所有后续通信都必须通过加密隧道进行[RFC5246]。

客户端必须在TLS握手过程中使用服务器名称指示扩展[RFC6066]。

一旦与服务器建立了连接(包括通过代理或TLS加密隧道建立的连接),客户端必须向服务器发送一个打开握手。这个握手由HTTP升级请求和必需及可选的头部字段列表组成。该握手的要求如下:

  1. 握手必须是按照[RFC2616]规定的有效HTTP请求。
  2. 请求的方法必须是GET,并且HTTP版本必须至少是1.1。

例如,如果WebSocket URI是"ws://example.com/chat",则发送的第一行应该是"GET /chat HTTP/1.1"。

  1. 请求的"Request-URI"部分必须与第3节中定义的/resource name/(相对URI)匹配,或者是一个绝对http/https URI,当解析时,其/resource name/、/host/和/port/必须与相应的ws/wss URI匹配。
  2. 请求必须包含一个|Host|头部字段,其值包含/host/,后面可选地跟着":"和/port/(当不使用默认端口时)。
  3. 请求必须包含一个|Upgrade|头部字段,其值必须包含"websocket"关键字。

  1. 请求必须包含一个|Connection|头部字段,其值必须包含"Upgrade"标记。
  2. 请求必须包含一个名为|Sec-WebSocket-Key|的头部字段。该头部字段的值必须是一个nonce(一次性的随机值),由随机选择的16字节值组成,并且已经进行了base64编码(参见[RFC4648]的第4节)。每个连接的nonce必须随机选择。

注意:例如,如果随机选择的值是字节序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,则头部字段的值为"AQIDBAUGBwgJCgsMDQ4PEC=="

  1. 如果请求来自浏览器客户端,则请求必须包含一个名为|Origin|[RFC6454]的头部字段。如果连接来自非浏览器客户端,并且该客户端的语义与这里描述的浏览器客户端用例相匹配,则该请求可以包含此头部字段。此头部字段的值是建立连接的代码所运行的上下文来源的ASCII序列化。有关如何构造此头部字段值的详细信息,请参阅[RFC6454]。

例如,如果从www.example.com下载的代码尝试与ww2.example.com建立连接,则头部字段的值为"http://www.example.com"。

  1. 请求必须包含一个名为|Sec-WebSocket-Version|的头部字段。该头部字段的值必须是13。

注意:尽管本文档的草案版本(-09、-10、-11和-12)已经发布(它们主要由编辑性更改和澄清组成,而不是对线路协议的更改),但值9、10、11和12并未用作Sec-WebSocket-Version的有效值。这些值在IANA注册表中被保留,但并未使用,将来也不会使用。

  1. 请求可以包含一个名为|Sec-WebSocket-Protocol|的头部字段。如果存在,此值表示客户端希望使用的一个或多个由逗号分隔的子协议,按照优先顺序排列。构成此值的元素必须是非空字符串,字符范围在U+0021到U+007E之间,不包括[RFC2616]中定义的分隔符字符,并且所有字符串都必须是唯一的。该头部字段值的ABNF(增强巴科斯-瑙尔范式)是1#token,其中构造和规则的定义如[RFC2616]所述。

11.请求可以包含一个名为|Sec-WebSocket-Extensions|的头部字段。如果存在,此值表示客户端希望使用的协议级扩展。该头部字段的解释和格式在9.1节中描述。

12.请求可以包含任何其他头部字段,例如cookies [RFC6265]和/或与身份验证相关的头部字段,如|Authorization|头部字段 [RFC2616],这些字段将根据其定义文档进行处理。

一旦客户端的打开握手请求已经发送,客户端在发送任何进一步的数据之前必须等待服务器的响应。客户端必须按照以下方式验证服务器的响应:

  1. 如果从服务器接收到的状态码不是101,客户端将按照HTTP [RFC2616]的规程处理该响应。特别是,如果客户端接收到401状态码,它可能会执行身份验证;服务器可能会使用3xx状态码来重定向客户端(但客户端没有义务遵循这些重定向),等等。否则,请按照以下步骤操作。
  2. 如果响应缺少|Upgrade|头部字段,或者|Upgrade|头部字段的值不是ASCII不区分大小写的“websocket”的匹配项,则客户端必须_终止WebSocket连接_。
  3. 如果响应缺少|Connection|头部字段,或者|Connection|头部字段不包含与ASCII不区分大小写的“Upgrade”值相匹配的标记,则客户端必须_终止WebSocket连接_。
  4. 如果响应缺少|Sec-WebSocket-Accept|头部字段,或者|Sec-WebSocket-Accept|包含的值不是由|Sec-WebSocket-Key|(作为字符串,不是base64解码的)与字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"拼接后的SHA-1哈希值的base64编码(忽略任何前后空白字符),则客户端必须_终止WebSocket连接_。
  5. 如果响应包含|Sec-WebSocket-Extensions|头部字段,并且此头部字段指示了客户端握手请求中不存在的扩展(服务器指示了一个客户端未请求的扩展),则客户端必须_终止WebSocket连接_。(关于如何解析此头部字段以确定哪些扩展被请求的讨论,见9.1节。)
  6. 如果响应包含|Sec-WebSocket-Protocol|头部字段,并且此头部字段指示了客户端握手请求中不存在的子协议(服务器指示了一个客户端未请求的子协议),则客户端必须_终止WebSocket连接_。

如果服务器的响应不符合本节和4.2.2节中定义的服务器握手要求,则客户端必须_终止WebSocket连接_。

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

如果服务器的响应按照上述规定进行了验证,则称_WebSocket连接已建立_,并且WebSocket连接处于OPEN状态。_使用中的扩展_被定义为一个(可能是空的)字符串,其值等于服务器握手时提供的|Sec-WebSocket-Extensions|头部字段的值,或者如果服务器握手时该头部字段不存在,则为空值。使用中的子协议_被定义为服务器握手中的|Sec-WebSocket-Protocol|头部字段的值,或者如果服务器握手时该头部字段不存在,则为空值。此外,如果服务器握手中的任何头部字段指示应设置cookies(如[RFC6265]中定义的),则这些cookies被称为_服务器打开握手期间设置的Cookies。

4.2. 服务器端要求

服务器可以将连接管理卸载给网络上的其他代理,例如负载均衡器和反向代理。在这种情况下,为了本规范的目的,服务器被认为包括从终止TCP连接的第一个设备开始,一直到处理请求和发送响应的服务器的整个服务器端基础设施的所有部分。

示例:一个数据中心可能有一个服务器,该服务器使用适当的握手响应WebSocket请求,然后将连接传递给另一个服务器以实际处理数据帧。为了本规范的目的,“服务器”是这两台计算机的组合。

4.2.1. 读取客户端的打开握手

当客户端开始一个WebSocket连接时,它会发送其打开握手的一部分。服务器必须解析该握手的至少一部分,以便获取生成握手服务器部分所需的信息。

客户端的打开握手包括以下部分。如果服务器在读取握手时发现客户端没有发送与下面描述相匹配的握手(请注意,根据[RFC2616],头部字段的顺序并不重要),包括但不限于握手组件指定的ABNF语法中的任何违规,服务器必须停止处理客户端的握手并返回一个带有适当错误码(如400 Bad Request)的HTTP响应。

  1. 一个HTTP/1.1或更高版本的GET请求,包括一个“Request-URI”[RFC2616],应被解释为第3节中定义的/资源名称/(或包含/资源名称/的绝对HTTP/HTTPS URI)。
  2. 一个|Host|头部字段,包含服务器的授权信息。
  3. 一个|Upgrade|头部字段,其值为“websocket”,被视为ASCII不区分大小写的值。
  4. 一个|Connection|头部字段,包含标记“Upgrade”,被视为ASCII不区分大小写的值。
  5. 一个|Sec-WebSocket-Key|头部字段,其值为base64编码的(参见[RFC4648]的第4节),解码后为16字节长。
  6. 一个|Sec-WebSocket-Version|头部字段,其值为13。
  7. 可选地,一个|Origin|头部字段。所有浏览器客户端都会发送这个头部字段。缺少此头部字段的连接尝试不应被解释为来自浏览器客户端。
  8. 可选地,一个|Sec-WebSocket-Protocol|头部字段,包含一系列值,指示客户端希望使用的协议,按优先级排序。
  9. 可选地,一个|Sec-WebSocket-Extensions|头部字段,包含一系列值,指示客户端希望使用的扩展。该头部字段的解释在第1节中讨论。
  10. 可选地,其他头部字段,如用于向服务器发送cookie或请求身份验证的字段。根据[RFC2616],未知的头部字段将被忽略。

4.2.2. 发送服务器的打开握手

当客户端与服务器建立WebSocket连接时,服务器必须完成以下步骤以接受连接并发送服务器的打开握手。

  1. 如果连接发生在HTTPS(HTTP-over-TLS)端口上,则在连接上执行TLS握手。如果这失败(例如,客户端在扩展的客户端hello "server_name"扩展中指示了一个服务器不托管的主机名),则关闭连接;否则,该连接的所有进一步通信(包括服务器的握手)必须通过加密隧道[RFC5246]进行。
  2. 服务器可以执行额外的客户端认证,例如,通过返回带有相应|WWW-Authenticate|头部字段的401状态码,如[RFC2616]所述。
  3. 服务器可以使用3xx状态码[RFC2616]将客户端重定向。请注意,此步骤可以与上述可选的认证步骤一起、之前或之后发生。
  4. 建立以下信息:

/origin/

客户端握手中的|Origin|头部字段表示建立连接的脚本的来源。该来源被序列化为ASCII并转换为小写。服务器可以将此信息用作确定是否接受传入连接的依据。如果服务器不验证来源,它将接受来自任何地方的连接。如果服务器不希望接受此连接,它必须返回一个适当的HTTP错误代码(例如,403 Forbidden)并中止本节中描述的WebSocket握手。更多详情,请参考第10节。

/key/

客户端握手中的|Sec-WebSocket-Key|头部字段包含一个base64编码的值,如果解码,则长度为16字节。此(编码)值在创建服务器握手时用于表示接受连接。服务器不需要对|Sec-WebSocket-Key|值进行base64解码。

/version/

客户端握手中的|Sec-WebSocket-Version|头部字段包含客户端尝试与之通信的WebSocket协议的版本。如果此版本与服务器理解的版本不匹配,服务器必须中止本节中描述的WebSocket握手,而是发送一个适当的HTTP错误代码(如426 Upgrade Required)和一个|Sec-WebSocket-Version|头部字段,指示服务器能够理解的版本。

/资源名/

服务器提供的服务的标识符。如果服务器提供多个服务,则该值应从客户端握手中给定的资源名中派生,该资源名位于GET方法的"Request-URI" [RFC2616]中。如果请求的服务不可用,服务器必须发送一个适当的HTTP错误代码(如404 Not Found)并中止WebSocket握手。

/子协议/

表示服务器准备使用的子协议的单个值或null。所选择的值必须从客户端的握手中派生,特别是通过选择服务器愿意用于此连接的|Sec-WebSocket-Protocol|字段中的一个值(如果有的话)。如果客户端的握手不包含这样的头部字段,或者如果服务器不同意客户端请求的任何子协议,那么唯一可接受的值就是null。该字段的缺失等同于null值(意味着如果服务器不希望同意任何建议的子协议,它必须在响应中不发送|Sec-WebSocket-Protocol|头部字段)。在这种情况下,空字符串与null值不同,并且不是此字段的合法值。此头部字段值的ABNF(增强巴科斯-诺尔范式)是(token),其中构造和规则的定义如[RFC2616]所述。

/扩展/

一个(可能为空)列表,表示服务器准备使用的协议级扩展。如果服务器支持多个扩展,则该值必须从客户端的握手中派生,特别是通过选择一个或多个|Sec-WebSocket-Extensions|字段中的值。如果缺少这样的字段,则等同于null值。在这些情况下,空字符串与null值不同。

客户端未列出的扩展不得列出。这些值的选择和解释方法将在第9.1节中讨论。

  1. 如果服务器选择接受传入的连接,它必须使用有效的HTTP响应来回复,以表示以下信息:
    1. 一个状态行,带有RFC 2616 [RFC2616] 定义的101响应代码。这样的响应可能看起来像“HTTP/1 101 Switching Protocols”。
    2. 一个|Upgrade|头部字段,其值为“websocket”,如RFC 2616 [RFC2616]所述。
    3. 一个|Connection|头部字段,其值为“Upgrade”。
    4. 一个|Sec-WebSocket-Accept|头部字段。该头部字段的值是通过将第2.2节中步骤4中定义的/key/与字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”连接,然后对这个连接值进行SHA-1哈希运算以获得一个20字节的值,并对这个20字节的哈希值进行base64编码(参见[RFC4648]的第4节)来构造的。

这个头部字段的ABNF [RFC2616]定义如下:

Sec-WebSocket-Accept = base64-value-non-empty

base64-value-non-empty = (1*base64-data [ base64-padding ]) | base64-padding

base64-data = 4base64-character

base64-padding = (2base64-character "==") | (3base64-character "=")

base64-character = ALPHA | DIGIT | "+" | "/"

注意:以下是一个示例,如果客户端握手中的|Sec-WebSocket-Key|头部字段的值为"dGhlIHNhbXBsZSBub25jZQ==",服务器会将字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"附加到该值后面,形成字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。然后,服务器会对这个字符串进行SHA-1哈希运算,得到值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。这个值随后会被base64编码,得到值"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",这个值将被返回在|Sec-WebSocket-Accept|头部字段中。

      

  1.   可选地,一个|Sec-WebSocket-Protocol|头部字段,其值/subprotocol/如第4.2.2节中步骤4所定义。
  2. 可选地,一个|Sec-WebSocket-Extensions|头部字段,其值/extensions/如第4.2.2节中步骤4所定义。如果需要使用多个扩展,它们都可以列在同一个|Sec-WebSocket-Extensions|头部字段中,或者分布在多个|Sec-WebSocket-Extensions|头部字段的实例中。

这完成了服务器的握手过程。如果服务器完成这些步骤而没有中止WebSocket握手,那么服务器将认为WebSocket连接已建立,并且WebSocket连接处于OPEN状态。此时,服务器可以开始发送(和接收)数据。

4.3. 握手过程中使用的新头部字段的收集ABNF

本节使用了[RFC2616]第2.1节中的ABNF语法/规则,包括“隐含的*LWS规则”。

请注意,本节中使用了以下ABNF约定。一些规则名称对应于相应头部字段的名称。这些规则表示相应头部字段的值,例如,Sec-WebSocket-Key ABNF规则描述了|Sec-WebSocket-Key|头部字段值的语法。名称中带有“-Client”后缀的ABNF规则仅用于客户端发送给服务器的请求;名称中带有“-Server”后缀的ABNF规则仅用于服务器发送给客户端的响应。例如,ABNF规则Sec-WebSocket-Protocol-Client描述了客户端发送给服务器的|Sec-WebSocket-Protocol|头部字段值的语法。

在握手过程中,客户端可以向服务器发送以下新的头部字段:

Sec-WebSocket-Key = base64-value-non-empty

Sec-WebSocket-Extensions = extension-list

Sec-WebSocket-Protocol-Client = 1#token

Sec-WebSocket-Version-Client = version

base64-value-non-empty = (1*base64-data [ base64-padding ]) | base64-padding

base64-data = 4base64-character

base64-padding = (2base64-character "==") | (3base64-character "=")

base64-character = ALPHA | DIGIT | "+" | "/"

extension-list = 1#extension

extension = extension-token *( ";" extension-param )

extension-token = registered-token

registered-token = token

extension-param = token [ "=" (token | quoted-string) ]

; 当使用quoted-string语法变体时,经过quoted-string转义后的值必须符合'token' ABNF。

NZDIGIT = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

version = DIGIT | (NZDIGIT DIGIT) | ("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)

; 限制在0-255范围内,不带前导零

在握手过程中,服务器可以向客户端发送以下新的头部字段:

Sec-WebSocket-Extensions = extension-list

Sec-WebSocket-Accept = base64-value-non-empty

Sec-WebSocket-Protocol-Server = token

Sec-WebSocket-Version-Server = 1#version

4.4. 支持WebSocket协议的多个版本

本节为客户端和服务器支持WebSocket协议的多个版本提供了一些指导。

通过使用WebSocket版本通告功能(即Sec-WebSocket-Version头部字段),客户端最初可以请求它偏好的WebSocket协议版本(这不一定是客户端支持的最新版本)。如果服务器支持所请求的版本,并且握手消息在其他方面都是有效的,服务器将接受该版本。如果服务器不支持所请求的版本,它必须使用Sec-WebSocket-Version头部字段(或多个Sec-WebSocket-Version头部字段)来响应,其中包含它愿意使用的所有版本。此时,如果客户端支持其中一个通告的版本,它可以使用新的版本值重复WebSocket握手过程。

以下示例展示了上述描述的版本协商过程:

客户端发送的请求可能如下:

http

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

...

Sec-WebSocket-Version: 25

服务器的响应可能如下:

http

HTTP/1.1 400 Bad Request

...

Sec-WebSocket-Version: 13, 8, 7

请注意,服务器的最后一个响应也可能看起来像这样:

http

HTTP/1.1 400 Bad Request

...

Sec-WebSocket-Version: 13

Sec-WebSocket-Version: 8, 7

(但通常,一个头部字段只包含一个值列表,而不是多个相同的头部字段包含不同的值列表。)

现在,客户端重复符合版本13的握手过程:

http

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

...

Sec-WebSocket-Version: 13

5. 数据帧

5.1 概述

在WebSocket协议中,数据是通过一系列帧进行传输的。为了避免混淆网络中介(如拦截代理)以及出于安全原因(在10.3节中进一步讨论),客户端必须掩码它发送给服务器的所有帧(有关更多详细信息,请参阅5.3节)。(请注意,无论WebSocket协议是否通过TLS运行,都会进行掩码操作。)服务器在接收到未掩码的帧时,必须关闭连接。在这种情况下,服务器可以使用在7.4.1节中定义的状态码1002(协议错误)来发送一个Close帧。服务器不得对其发送给客户端的任何帧进行掩码。如果客户端检测到掩码的帧,它必须关闭连接。在这种情况下,它可以使用在7.4.1节中定义的状态码1002(协议错误)。(这些规则在未来的规范中可能会有所放宽。)

基础帧协议定义了一个帧类型,该类型包括一个操作码(opcode)、有效载荷长度,以及为“扩展数据”和“应用数据”指定的位置,它们共同定义了“有效载荷数据”。某些位和操作码是为将来扩展协议而保留的。

在完成握手并打开连接后,以及在该端点发送Close帧(5.5.1节)之前,客户端或服务器都可以随时传输数据帧。

5.2 基础帧协议

数据传输部分的这种线格式由本节中详细给出的ABNF(增强的巴科斯-诺尔范式) [RFC5234] 描述。(请注意,与本文档的其他部分不同,本节中的ABNF是对位组的操作。每个位组的长度在注释中指示。在电线上编码时,最左边的位是最重要的位,也是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 | 扩展有效载荷长度 |

|I|S|S|S| (4) |A| (7) | (16/64) |

|N|V|V|V| |S| | (如果有效载荷长度==126/127) |

| |1|2|3| |K| | |

+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

| 如果有效载荷长度 == 127,则继续扩展有效载荷长度 |

+ - - - - - - - - - - - - - - - +-------------------------------+

| |如果设置了MASK,则为掩码密钥 |

+-------------------------------+-------------------------------+

| 掩码密钥(续) | 有效载荷数据 |

+-------------------------------- - - - - - - - - - - - - - - - +

: 有效载荷数据继续... :

+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

| 有效载荷数据继续... |

+---------------------------------------------------------------+

FIN: 1位

指示这是消息中的最后一个片段。第一个片段也可能是最后一个片段。

RSV1、RSV2、RSV3:每位各1比特

除非协商了定义非零值含义的扩展,否则这些位必须为0。如果接收到非零值,且协商的任何扩展都没有定义这种非零值的含义,则接收端点必须终止WebSocket连接。

操作码(Opcode): 4位

定义了“有效载荷数据”的解释。如果接收到未知的操作码,接收端点必须终止WebSocket连接。已定义以下值。

  • %x0 表示延续帧
  • %x1 表示文本帧
  • %x2 表示二进制帧
  • %x3-7 保留用于进一步的非控制帧
  • %x8 表示连接关闭
  • %x9 表示ping
  • %xA 表示pong
  • %xB-F 保留用于进一步的控制帧

掩码(Mask): 1位

定义了“有效载荷数据”是否已掩码。如果设置为1,则在掩码键(masking-key)中存在一个掩码密钥,并且这用于根据第5.3节的规定对“有效载荷数据”进行解掩码。从客户端发送到服务器的所有帧都将此位设置为1。

有效载荷长度(Payload length): 7位、7+16位或7+64位

“有效载荷数据”的长度(以字节为单位):如果为0-125,则这是有效载荷长度。如果为126,则接下来的2个字节被解释为16位无符号整数,表示有效载荷长度。如果为127,则接下来的8个字节被解释为64位无符号整数(最高有效位必须为0),表示有效载荷长度。多字节长度值以网络字节序表示。请注意,在所有情况下,必须使用最小数量的字节来编码长度,例如,长度为124字节的字符串不能编码为序列126, 0, 124。有效载荷长度是“扩展数据”的长度加上“应用程序数据”的长度。“扩展数据”的长度可能为零,在这种情况下,有效载荷长度是“应用程序数据”的长度。

掩码键(Masking-key): 0字节或4字节

从客户端发送到服务器的所有帧都使用帧内包含的32位值进行掩码。如果掩码位设置为1,则此字段存在;如果掩码位设置为0,则此字段不存在。有关客户端到服务器掩码的更多信息,请参阅第5.3节。

有效载荷数据(Payload data): (x+y) 字节

“有效载荷数据”定义为“扩展数据”与“应用程序数据”的串联。

扩展数据(Extension data): x 字节

除非已协商扩展,否则“扩展数据”为0字节。任何扩展都必须指定“扩展数据”的长度,或如何计算该长度,以及必须在打开握手期间如何协商该扩展的使用。如果存在,“扩展数据”包含在总有效载荷长度中。

应用程序数据(Application data): y 字节

任意“应用程序数据”,占据帧中任何“扩展数据”之后的剩余部分。“应用程序数据”的长度等于有效载荷长度减去“扩展数据”的长度。

基础帧协议由以下ABNF(增强巴科斯-诺尔范式)正式定义[RFC5234]。重要的是要注意,这种数据的表示形式是二进制的,而不是ASCII字符。因此,长度为1位且取值为%x0/%x1的字段表示为单个位,其值为0或1,而不是表示ASCII编码中字符“0”或“1”的完整字节(八位组)。长度为4位且取值在%x0-F之间的字段再次由4位表示,同样不是由具有这些值的ASCII字符或完整字节(八位组)表示。[RFC5234]没有指定字符编码:“规则解析为终端值字符串,有时称为字符。在ABNF中,字符仅是非负整数。在某些上下文中,将指定值到字符集(如ASCII)的特定映射(编码)。” 在这里,指定的编码是二进制编码,其中每个终端值以指定数量的位进行编码,每个字段的位数不同。

ws-frame                = frame-fin           ; 长度为1位 

                          frame-rsv1          ; 长度为1位 

                          frame-rsv2          ; 长度为1位 

                          frame-rsv3          ; 长度为1位(未在直接给出的定义中列出,但应有同样的1位长度) 

                          frame-opcode        ; 长度为4位 

                          frame-masked        ; 长度为1位 

                          frame-payload-length   ; 长度可能为7位、7+16位或7+64位 

                          [ frame-masking-key ]  ; 长度为32位(可选) 

                          frame-payload-data     ; 长度为n*8位,其中n >= 0 

 

frame-fin               = %x0 ; 表示还有更多该消息的帧跟随 

                        / %x1 ; 表示这是该消息的最后一帧 

                              ; 长度为1位 

 

frame-rsv1              = %x0 或 %x1 

                          ; 长度为1位,除非另有协商,否则必须为0 

 

frame-rsv2              = %x0 或 %x1 

                          ; 长度为1位,除非另有协商,否则必须为0

frame-rsv3              = %x0 / %x1 

                          ; 长度为1位,除非另有协商,否则必须为0

frame-opcode            = frame-opcode-non-control / 

                          frame-opcode-control / 

                          frame-opcode-cont 

 

frame-opcode-cont       = %x0 ; 连续帧 

 

frame-opcode-non-control= %x1 ; 文本帧 

                          / %x2 ; 二进制帧 

                          / %x3-7 

                          ; 长度为4位, 

                          ; 保留给未来非控制帧使用 

 

frame-opcode-control    = %x8 ; 连接关闭 

                          / %x9 ; Ping 

                          / %xA ; Pong 

                          / %xB-F ; 保留给未来控制帧使用 

                                  ; 长度为4位

frame-masked            = %x0 

                            ; 帧未被掩码,没有帧掩码键 

                            / %x1 

                            ; 帧已被掩码,存在帧掩码键 

                            ; 长度为1位 

 

frame-payload-length    = ( %x00-7D ) 

                            / ( %x7E frame-payload-length-16 ) 

                            / ( %x7F frame-payload-length-63 ) 

                            ; 长度分别为7位、7+16位或7+64位 

 

frame-payload-length-16 = %x0000-FFFF ; 长度为16位 

 

frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF 

                            ; 长度为64位 

 

frame-masking-key       = 4( %x00-FF ) 

                              ; 仅在 frame-masked 为 1 时存在 

                              ; 长度为32位 

 

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. 客户端到服务器的掩码

一个被掩码的帧必须将字段 frame-masked 设置为 1,如第 5.2 节所定义。

掩码键完全包含在帧内,如第 5.2 节所定义的 frame-masking-key。它用于掩码同一节中定义的“Payload data”(有效载荷数据),即 frame-payload-data,这包括“Extension data”(扩展数据)和“Application data”(应用数据)。

掩码键是由客户端随机选择的 32 位值。在准备一个被掩码的帧时,客户端必须从允许的 32 位值集合中选择一个新的掩码键。掩码键需要是不可预测的;因此,掩码键必须来源于一个强大的熵源,并且对于给定的帧,掩码键必须不能让服务器/代理简单地预测后续帧的掩码键。掩码键的不可预测性是防止恶意应用程序的作者选择出现在线路上的字节的关键。RFC 4086 [RFC4086] 讨论了对于安全敏感的应用程序来说,什么构成了合适的熵源。

掩码不会影响“Payload data”(有效载荷数据)的长度。要将掩码数据转换为未掩码数据,或进行反向转换,应用以下算法。无论转换的方向如何(即将数据掩码或解掩码),都应用相同的算法。

转换后数据的第 i 个八位字节(“transformed-octet-i”)是原始数据的第 i 个八位字节(“original-octet-i”)与掩码键中索引为 i mod 4 的八位字节(“masking-key-octet-j”)的异或(XOR)结果:

j = i MOD 4

transformed-octet-i = original-octet-i XOR masking-key-octet-j

在帧结构中指示的有效载荷长度(即 frame-payload-length)不包括掩码键的长度。它指的是“Payload data”的长度,即掩码键之后跟随的字节数。

5.4. 分片

分片的主要目的是允许在消息开始时发送未知大小的消息,而无需对该消息进行缓冲。如果消息不能分片,那么一个端点将不得不缓冲整个消息以便在发送第一个字节之前计算其长度。通过分片,服务器或中间代理可以选择一个合理大小的缓冲区,当缓冲区满时,将一个片段写入网络。

分片的次要用例是用于多路复用,其中不希望一个逻辑通道上的大消息独占输出通道,因此多路复用需要自由地将消息拆分成更小的片段以更好地共享输出通道。(请注意,本文档未描述多路复用扩展。)

除非扩展另有规定,否则帧没有语义意义。如果客户端和服务器没有协商任何扩展,或者虽然协商了一些扩展,但中间代理理解了所有协商的扩展并知道如何在这些扩展存在的情况下合并和/或拆分帧,那么中间代理可能会合并和/或拆分帧。这意味着在没有扩展的情况下,发送者和接收者不得依赖于特定帧边界的存在。

以下规则适用于分片:

o 一个未分片的消息由一个设置了FIN位的单个帧(第5.2节)和除0之外的操作码组成。

o 一个分片的消息由一个设置了FIN位(清除状态)且操作码非0的单个帧开始,随后是零个或多个FIN位(清除状态)且操作码设置为0的帧,最后以一个设置了FIN位且操作码为0的单个帧结束。从概念上讲,一个分片的消息等同于一个较大的单一消息,其有效载荷等于按序排列的各个片段的有效载荷的拼接;但是,在存在扩展的情况下,这可能不成立,因为扩展定义了“扩展数据”的解释。例如,“扩展数据”可能仅存在于第一个片段的开头,并应用于后续片段,或者每个片段中都可能存在仅适用于该特定片段的“扩展数据”。在没有“扩展数据”的情况下,以下示例展示了分片的工作原理。

示例:对于作为三个片段发送的文本消息,第一个片段的操作码为0x1且FIN位清除,第二个片段的操作码为0x0且FIN位清除,第三个片段的操作码为0x0且FIN位设置。

o 控制帧(见第5.5节)可以在分片消息的中间插入。但控制帧本身不得被分片。

o 消息片段必须按照发送者发送的顺序交付给接收者。

o 一个消息的片段不得与其他消息的片段交织在一起,除非已协商了可以解释交织的扩展。

o 端点必须能够处理分片消息中的控制帧。

o 发送者可以为非控制消息创建任何大小的分片。

o 客户端和服务器必须支持接收分片和未分片的消息。

o 由于控制帧不能被分片,因此中间代理不得尝试更改控制帧的分片。

o 如果使用了任何保留位值且中间代理不知道这些值的含义,则它不得更改消息的分片。

o 在已协商扩展并且中间代理不了解已协商扩展的语义的连接上下文中,中间代理不得更改任何消息的分片。类似地,没有见证导致WebSocket连接的WebSocket握手(也没有被通知其内容)的中间代理不得更改该连接中任何消息的分片。

o 由于这些规则的结果,消息的所有片段都是相同类型的,由第一个片段的操作码设置。由于控制帧不能被分片,因此消息中所有片段的类型必须是文本、二进制或保留的操作码之一。

注意:如果控制帧不能插入,例如,位于大消息后面的ping的延迟将会非常长。因此,需要在分片消息的中间处理控制帧。

实现注意事项:在没有任何扩展的情况下,接收者不需要缓冲整个帧以便处理它。例如,如果使用流式API,可以将帧的一部分交付给应用程序。但是,请注意,这个假设可能不适用于所有未来的WebSocket扩展。

5.5. 控制帧

控制帧通过操作码进行标识,其中操作码的最高位为1。当前定义的控制帧操作码包括0x8(关闭)、0x9(Ping)和0xA(Pong)。操作码0xB-0xF是为将来要定义的其他控制帧保留的。

控制帧用于通信WebSocket的状态。控制帧可以插入分片消息的中间。

所有控制帧的负载长度必须为125字节或更少,并且不得被分片。

5.5.1. 关闭

关闭帧包含操作码0x8。

关闭帧可能包含一个主体(帧的“应用程序数据”部分),该主体指示关闭的原因,例如端点关闭、端点接收到过大的帧,或者端点接收到不符合端点期望格式的帧。如果存在主体,主体的前两个字节必须是一个2字节的无符号整数(以网络字节序),表示在7.4节中定义的状态码/code/。在2字节整数之后,主体可能包含UTF-8编码的数据,其值为/reason/,但本规范未定义其解释。这些数据不一定是人类可读的,但可能对调试或向打开连接的脚本传递相关信息有用。由于数据不保证是人类可读的,因此客户端不得向最终用户显示它。

从客户端发送到服务器的关闭帧必须按照5.3节的要求进行掩码处理。

在发送关闭帧后,应用程序不得再发送任何数据帧。

如果一个端点收到关闭帧且之前没有发送过关闭帧,那么该端点必须发送一个关闭帧作为响应。(在发送响应的关闭帧时,端点通常会回显它接收到的状态码。)它应该尽快这样做。端点可能会延迟发送关闭帧,直到其当前消息发送完毕(例如,如果大部分分片消息已经发送,端点可能会在发送关闭帧之前先发送剩余的片段)。但是,已经发送了关闭帧的端点是否会继续处理数据则没有保证。

在发送和接收关闭消息后,一个端点将认为WebSocket连接已关闭,并且必须关闭底层的TCP连接。服务器必须立即关闭底层的TCP连接;客户端应该等待服务器关闭连接,但在发送和接收关闭消息后,可以随时关闭连接,例如,如果它在合理的时间段内没有收到来自服务器的TCP关闭。

如果客户端和服务器同时发送关闭消息,则两个端点都将发送并接收关闭消息,并应考虑WebSocket连接已关闭并关闭底层的TCP连接。

5.5.2. Ping

Ping帧包含操作码0x9。

Ping帧可以包含“应用程序数据”。

在接收到Ping帧后,除非已经收到Close帧,否则端点必须发送一个Pong帧作为响应。它应该尽快以实际可行的方式响应。Pong帧在第5.5.3节中讨论。

在连接建立后且在连接关闭前,端点可以随时发送Ping帧。

注意:Ping帧可以用作保持连接活跃的手段,也可以用作验证远程端点是否仍然响应的方法。

5.5.3. Pong

Pong帧包含操作码0xA。

第5.5.2节详细说明了适用于Ping帧和Pong帧的要求。

作为对Ping帧的响应而发送的Pong帧必须具有与所回复的Ping帧的消息体中的“应用程序数据”完全相同的“应用程序数据”。

如果端点接收到Ping帧且尚未发送对先前Ping帧的Pong帧响应,则端点可以选择仅发送对最近处理的Ping帧的Pong帧。

Pong帧可以主动发送。这用作单向心跳。不需要对主动发送的Pong帧进行响应。

5.6. 数据帧

数据帧(例如,非控制帧)通过操作码来识别,其中操作码的最高位为0。目前定义的数据帧操作码包括0x1(文本)、0x2(二进制)。操作码0x3-0x7是为将来要定义的非控制帧预留的。

数据帧携带应用层和/或扩展层数据。操作码决定了数据的解释方式:

文本

“有效载荷数据”是编码为UTF-8的文本数据。请注意,特定的文本帧可能包含部分UTF-8序列;但是,整个消息必须包含有效的UTF-8。重组消息中的无效UTF-8将按照第8.1节所述的方式处理。

二进制

“有效载荷数据”是任意二进制数据,其解释完全取决于应用层。

5.7. 示例

o 单帧未掩码的文本消息

0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f(包含“Hello”)

o 单帧掩码的文本消息

0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58(包含“Hello”)

o 分片的未掩码文本消息

0x01 0x03 0x48 0x65 0x6c(包含“Hel”)

0x80 0x02 0x6c 0x6f(包含“lo”)

o 未掩码的Ping请求和掩码的Ping响应

0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f(包含主体内容为“Hello”,但主体内容是任意的)

0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58(包含主体内容为“Hello”,与Ping的主体内容相匹配)

o 单个未掩码帧中的256字节二进制消息

0x82 0x7E 0x0100 [256字节的二进制数据]

o 单个未掩码帧中的64KiB二进制消息

0x82 0x7F 0x0000000000010000 [65536字节的二进制数据]

5.8. 可扩展性

该协议设计为允许扩展,这些扩展将为基本协议增加功能。连接的端点必须在打开握手期间协商任何扩展的使用。本规范为扩展提供了操作码0x3到0x7和0xB到0xF、“扩展数据”字段,以及帧头中的frame-rsv1、frame-rsv2和frame-rsv3位。关于扩展的协商将在第9.1节中进一步详细讨论。以下是扩展的一些预期用途。这个列表既不完整也不具有规定性。

o “扩展数据”可以放在“应用程序数据”之前的“有效载荷数据”中。

o 可以为每帧需求分配保留位。

o 可以定义保留的操作码值。

o 如果需要更多的操作码值,可以将保留位分配给操作码字段。

o 可以定义一个保留位或“扩展”操作码,该操作码从“有效载荷数据”中分配额外的位来定义更大的操作码或更多的每帧位。

6. 发送和接收数据

6.1. 发送数据

要通过WebSocket连接发送由/data/组成的WebSocket消息,端点必须执行以下步骤。

  1. 端点必须确保WebSocket连接处于OPEN状态(参见第4.1节和第4.2.2节)。如果在任何时候WebSocket连接的状态发生变化,端点必须中止以下步骤。
  2. 端点必须将/data/封装在WebSocket帧中,如第5.2节所定义。如果要发送的数据很大,或者端点希望在开始发送数据时数据尚未全部可用,端点也可以将数据封装在一系列帧中,如第5.4节所定义。
  3. 包含数据的第一个帧的操作码(frame-opcode)必须设置为第5.2节中定义的适当值,以便接收方将数据解释为文本或二进制数据。
  4. 包含数据的最后一个帧的FIN位(frame-fin)必须设置为1,如第5.2节所定义。
  5. 如果数据是由客户端发送的,帧(或帧序列)必须按照第5.3节中的定义进行掩码处理。
  6. 如果已为WebSocket连接协商了任何扩展(第9节),则可能需要根据这些扩展的定义进行其他考虑。
  7. 已形成的帧(或帧序列)必须通过底层网络连接进行传输。

6.2. 接收数据

为了接收WebSocket数据,一个端点会监听底层的网络连接。收到的数据必须按照5.2节定义的WebSocket帧进行解析。如果收到一个控制帧(5.5节),则必须按照5.5节定义的方式处理该帧。在收到一个数据帧(5.6节)时,端点必须注意由操作码(frame-opcode)定义的数据的/类型/(type),该操作码定义在5.2节。这个帧中的“应用数据”(Application data)被定义为消息的/数据/(data)。如果帧包含一个未分片的消息(5.4节),那么就说_已接收到WebSocket消息_,类型为/类型/(type)且数据为/数据/(data)。如果帧是分片消息的一部分,则将后续数据帧的“应用数据”连接起来以形成/数据/(data)。当接收到由FIN位(frame-fin)指示的最后一个片段时,就说_已接收到WebSocket消息_,其数据/数据/(data)由片段的“应用数据”的连接组成,类型为/类型/(type)(从分片消息的第一个帧中注明)。后续的数据帧必须被解释为属于新的WebSocket消息。

扩展(9节)可能会改变读取数据的语义,特别包括什么构成一个消息边界。除了在有效载荷的“应用数据”之前添加“扩展数据”之外,扩展还可以修改“应用数据”(例如通过压缩它)。

服务器必须如5.3节所述,移除从客户端接收到的数据帧的掩码。

7. 关闭连接

7.1. 定义

7.1.1. 关闭WebSocket连接

为了_关闭WebSocket连接_,一个端点会关闭底层的TCP连接。端点应该使用一种能够干净地关闭TCP连接(以及如果适用的话TLS会话)的方法,丢弃可能收到的任何尾部字节。在必要时,如遭受攻击时,端点可以通过任何可用手段关闭连接。

在大多数情况下,底层的TCP连接应该首先由服务器关闭,这样服务器将保持TIME_WAIT状态而不是客户端(因为这会阻止客户端在最大段生存期(2MSL)内重新打开连接,而服务器则没有相应的影响,因为当收到带有更高序列号的新SYN时,TIME_WAIT连接会立即重新打开)。在异常情况(如在合理时间后没有收到来自服务器的TCP关闭)下,客户端可以发起TCP关闭。因此,当服务器被指示_关闭WebSocket连接_时,它应该立即发起TCP关闭,而当客户端被指示做同样的事情时,它应该等待来自服务器的TCP关闭。

以使用Berkeley套接字的C语言为例,要获得干净的关闭,可以调用shutdown()函数在套接字上设置SHUT_WR,然后调用recv()直到获得返回值为0,表示对端也已经执行了有序的关闭,最后调用close()关闭套接字。

7.1.2. 开始WebSocket关闭握手

为了使用状态码(7.4节)/code/和可选的关闭原因(7.1.6节)/reason/来_开始WebSocket关闭握手_,一个端点必须发送一个Close控制帧,如5.5.1节所述,该帧的状态码设置为/code/,关闭原因设置为/reason/。一旦一个端点既发送又接收了一个Close控制帧,该端点应该按照7.1.1节的定义_关闭WebSocket连接_。

7.1.3. WebSocket关闭握手已开始

在发送或接收Close控制帧之后,可以说_WebSocket关闭握手已开始_,并且WebSocket连接处于CLOSING状态。

7.1.4. WebSocket连接已关闭

当底层的TCP连接关闭时,可以说_WebSocket连接已关闭_,并且WebSocket连接处于CLOSED状态。如果TCP连接在WebSocket关闭握手完成后被关闭,则称WebSocket连接是_干净关闭_的。

如果WebSocket连接无法建立,也可以说_WebSocket连接已关闭_,但不是_干净关闭_的。

7.1.5. WebSocket连接关闭代码

如5.5.1节和7.4节所定义,Close控制帧可能包含一个状态码,表明关闭的原因。WebSocket连接的关闭可以由任一端点发起,且可能同时发生。_WebSocket连接关闭代码_定义为实现此协议的应用程序接收到的第一个Close控制帧中的状态码(7.4节)。如果此Close控制帧不包含状态码,则_WebSocket连接关闭代码_被认为是1005。如果_WebSocket连接已关闭_且端点没有收到Close控制帧(例如,底层传输连接丢失可能发生这种情况),则_WebSocket连接关闭代码_被认为是1006。

注意:两个端点可能无法就_WebSocket连接关闭代码_的值达成一致。例如,如果远程端点发送了一个Close帧,但本地应用程序尚未从其套接字的接收缓冲区中读取包含Close帧的数据,并且本地应用程序独立决定关闭连接并发送一个Close帧,那么两个端点都将发送并接收一个Close帧,并且不会发送进一步的Close帧。每个端点都将看到由另一端发送的状态码作为_WebSocket连接关闭代码_。因此,在两个端点独立且几乎同时_开始WebSocket关闭握手_的情况下,两个端点可能无法就_WebSocket连接关闭代码_的值达成一致。

7.1.6. WebSocket连接关闭原因

如5.5.1节和7.4节所定义,Close控制帧可能包含一个状态码来指示关闭的原因,随后是UTF-8编码的数据,对这些数据的解释由端点自行决定,本协议不做定义。WebSocket连接的关闭可以由任一端点发起,且可能同时发生。_WebSocket连接关闭原因_定义为实现此协议的应用程序接收到的第一个Close控制帧中状态码(7.4节)之后的UTF-8编码数据。如果Close控制帧中没有这样的数据,则_WebSocket连接关闭原因_为空字符串。

注意:与7.1.5节所述的逻辑相同,两个端点可能无法就_WebSocket连接关闭原因_达成一致。

7.1.7. 终止WebSocket连接

某些算法和规范要求端点_终止WebSocket连接_。为此,客户端必须_关闭WebSocket连接_,并且可以用适当的方式向用户报告问题(这对于开发人员尤其有用)。类似地,为此,服务器必须_关闭WebSocket连接_,并应该记录问题。

如果在要求端点_终止WebSocket连接_之前_WebSocket连接已经建立_,端点在继续_关闭WebSocket连接_之前应该发送一个带有适当状态码(7.4节)的Close帧。如果端点认为由于导致WebSocket连接首先失败的错误的性质,另一端不太可能接收和处理Close帧,则端点可以选择不发送Close帧。在接到指令_终止WebSocket连接_后,端点不得继续尝试处理来自远程端点的数据(包括响应的Close帧)。

除上述指示或应用层(例如,使用WebSocket API的脚本)所指定的情况外,客户端不应关闭连接。

7.2. 异常关闭

7.2.1. 客户端发起的关闭

某些算法,特别是在打开握手期间,要求客户端_终止WebSocket连接_。为此,客户端必须按照7.1.7节中的定义_终止WebSocket连接_。

如果在任何时候底层传输层连接意外丢失,客户端必须_终止WebSocket连接_。

除上述指示或应用层所指定的情况(例如,使用WebSocket API的脚本)外,客户端不应关闭连接。

7.2.2. 服务器发起的关闭

某些算法要求在打开握手期间服务器_终止WebSocket连接_,或者推荐这样做。为此,服务器只需_关闭WebSocket连接_(7.1.1节)。

7.2.3. 从异常关闭中恢复

异常关闭可能由多种原因造成。这样的关闭可能是由瞬时错误导致的,在这种情况下,重新连接可能会导致良好的连接并恢复正常操作。这样的关闭也可能是由非瞬时问题导致的,在这种情况下,如果每个部署的客户端都遇到异常关闭并立即持续尝试重新连接,服务器可能会经历由大量尝试重新连接的客户端造成的拒绝服务攻击。这种场景的最终结果可能是服务无法及时恢复,或者恢复变得更加困难。

为了防止这种情况,客户端在尝试从异常关闭中重新连接时应该使用某种形式的退避策略,如本节所述。

首次重新连接尝试应该被随机延迟一段时间。这种随机延迟的参数由客户端决定;在0到5秒之间随机选择一个值是合理的初始延迟,但客户端可以根据实现经验和特定应用选择不同的时间间隔来选择延迟长度。

如果首次重新连接尝试失败,后续的重新连接尝试应该使用越来越长的时间延迟,例如采用截断二进制指数退避方法。

7.3. 连接的正常关闭

服务器可以在任何需要的时候关闭WebSocket连接。客户端不应该随意关闭WebSocket连接。在任何情况下,端点都通过遵循_启动WebSocket关闭握手_(7.1.2节)的程序来发起关闭。

7.4. 状态码

在关闭已建立的连接时(例如,在发送Close帧之后,在打开握手完成后),端点可以指示关闭的原因。该规范未定义端点对关闭原因的解释,以及端点在给定原因时应采取的操作。本规范定义了一组预定义的状态码,并指定了扩展、框架和终端应用可以使用哪些范围。状态码和任何相关的文本消息都是Close帧的可选组件。

7.4.1. 定义的状态码

端点在发送Close帧时,可以使用以下预定义的状态码。

1000

1000 表示正常关闭,意味着建立连接的目的已经达成。

1001

1001 表示端点“正在离开”,例如服务器正在关闭或浏览器已从页面导航离开。

1002

1002 表示端点由于协议错误而终止连接。

1003

1003 表示端点正在终止连接,因为它收到了它无法接受的数据类型(例如,仅理解文本数据的端点在收到二进制消息时可能会发送此状态码)。

1004

保留。具体含义可能会在将来定义。

1005

1005 是一个保留值,端点必须在Close控制帧中设置状态码时避免使用它。它被指定用于那些期望状态码表示实际上没有状态码存在的应用。

1006

1006 是一个保留值,端点必须在Close控制帧中设置状态码时避免使用它。它被指定用于那些期望状态码表示连接异常关闭的应用,例如,没有发送或接收Close控制帧就关闭了连接。

1007

1007 表示端点正在终止连接,因为它在消息中收到了与消息类型不一致的数据(例如,在文本消息中收到了非UTF-8 [RFC3629] 数据)。

1008

1008 表示端点正在终止连接,因为它收到了违反其策略的消息。这是一个通用的状态码,当没有更合适的状态码(例如,1003或1009)可用时,或者需要隐藏策略的具体细节时,可以返回此状态码。

1009

1009 表示端点正在终止连接,因为它收到了它无法处理的过大消息。

1010

1010 表示一个端点(客户端)正在终止连接,因为它期望服务器协商一个或多个扩展,但服务器在WebSocket握手的响应消息中没有返回它们。所需扩展的列表应该出现在Close帧的/reason/部分。请注意,此状态码不由服务器使用,因为它可以选择失败WebSocket握手。

1011

1011 表示服务器正在终止连接,因为它遇到了一个意外的条件,导致它无法完成请求。

1015

1015 是一个保留值,端点必须在Close控制帧中设置状态码时避免使用它。它被指定用于那些期望状态码表示连接因未能执行TLS握手(例如,服务器证书无法验证)而关闭的应用。

7.4.2. 保留的状态码范围

0-999

0-999范围内的状态码未被使用。

1000-2999

1000-2999范围内的状态码保留给本协议、其未来修订版和永久且易于获取的公共规范中指定的扩展定义使用。

3000-3999

3000-3999范围内的状态码保留给库、框架和应用程序使用。这些状态码直接向IANA注册。本协议未定义这些代码的解释。

4000-4999

4000-4999范围内的状态码保留为私有使用,因此无法注册。WebSocket应用程序之间可以通过事先的协议来使用这些代码。本协议未定义这些代码的解释。

  1. 错误处理

8.1. 处理UTF-8编码数据中的错误

当一个端点要将字节流解释为UTF-8编码,但发现该字节流实际上并不是有效的UTF-8流时,该端点必须_终止WebSocket连接_。此规则既适用于握手期间,也适用于后续的数据交换。

  1. 扩展

WebSocket客户端可以请求本规范的扩展,而WebSocket服务器可以接受客户端请求的部分或全部扩展。服务器不得响应客户端未请求的任何扩展。如果客户端和服务器之间的协商中包含了扩展参数,这些参数必须根据适用扩展的规范来选择。

9.1. 协商扩展

客户端通过包含 |Sec-WebSocket-Extensions| 头字段来请求扩展,该头字段遵循HTTP头字段的正常规则(参见[RFC2616],第4.2节),并且头字段的值由以下ABNF(增强巴科斯-诺尔范式)[RFC2616]定义。请注意,本节使用的是[RFC2616]中的ABNF语法/规则,包括“隐含的*LWS规则”。如果在协商过程中客户端或服务器接收到不符合以下ABNF的值,接收到此类格式错误数据的接收方必须立即_终止WebSocket连接_。

Sec-WebSocket-Extensions = extension-list

extension-list = 1#extension

extension = extension-token *( ";" extension-param )

extension-token = registered-token

registered-token = token

extension-param = token [ "=" (token | quoted-string) ]

; 当使用quoted-string语法变体时,经过quoted-string转义后的值

; 必须符合'token' ABNF。

请注意,与其他HTTP头字段一样,此头字段可能跨多行拆分或组合。因此,以下两种方式是等效的:

Sec-WebSocket-Extensions: foo

Sec-WebSocket-Extensions: bar; baz=2

完全等价于

Sec-WebSocket-Extensions: foo, bar; baz=2

使用的任何extension-token都必须是已注册的token(参见第11.4节)。为任何给定扩展提供的参数必须针对该扩展定义。请注意,客户端仅提供使用任何广告扩展的提议,并且除非服务器指示希望使用扩展,否则不得使用它们。

请注意,扩展的顺序很重要。多个扩展之间的任何交互可能在定义这些扩展的文档中定义。在没有此类定义的情况下,解释是客户端在其请求中列出的头字段表示它希望使用的头字段的偏好,列出的第一个选项是最可取的。服务器在响应中列出的扩展表示连接实际使用的扩展。如果扩展修改了数据和/或帧结构,那么对数据上的操作顺序应假定为与服务器在握手时响应中列出的扩展顺序相同。

例如,如果有两个扩展“foo”和“bar”,如果服务器发送的 |Sec-WebSocket-Extensions| 头字段的值为“foo, bar”,那么对数据的操作将按照 bar(foo(data)) 的顺序进行,无论是对数据本身的更改(如压缩)还是对可能“堆叠”的帧结构的更改。

可接受的扩展头字段的非规范示例(请注意,长行已折叠以提高可读性):

Sec-WebSocket-Extensions: deflate-stream

Sec-WebSocket-Extensions: mux; max-channels=4; flow-control,

deflate-stream

Sec-WebSocket-Extensions: private-extension

服务器通过包含一个 |Sec-WebSocket-Extensions| 头字段来接受一个或多个扩展,该头字段包含一个或多个客户端请求的扩展。任何扩展参数的解释,以及服务器对客户端请求的一组参数的有效响应是什么,将由每个这样的扩展定义。

9.2. 已知扩展

扩展提供了一种机制,允许实现选择性地加入额外的协议特性。本文档没有定义任何扩展,但实现可以使用单独定义的扩展。

安全考虑

本节描述了一些适用于WebSocket协议的安全考虑。本节的子节描述了特定的安全考虑。

10.1. 非浏览器客户端

WebSocket协议通过检查 |Origin| 头字段(见下文)等方式,保护免受在受信任的应用程序(如Web浏览器)内部运行的恶意JavaScript的侵害。更多细节请参见第1.6节。这种假设在功能更强大的客户端的情况下并不成立。

虽然此协议旨在由网页中的脚本使用,但它也可以直接由主机使用。这些主机以自己的名义行事,因此可以发送伪造的 |Origin| 头字段,误导服务器。因此,服务器应该谨慎地假设它们正在与来自已知来源的脚本直接通信,并必须考虑到它们可能会以意外的方式被访问。特别是,服务器不应该信任任何输入都是有效的。

示例:如果服务器将输入用作SQL查询的一部分,那么在将输入文本传递给SQL服务器之前,所有输入文本都应该进行转义,以免服务器容易受到SQL注入的攻击。

10.2. Origin的考虑

不打算处理任何网页的输入但仅为特定站点处理的服务器应该验证 |Origin| 字段是否是他们期望的来源。如果指示的来源对于服务器来说是不可接受的,那么它应该以包含HTTP 403 Forbidden状态代码的回复响应WebSocket握手。

|Origin| 头字段用于保护那些通常由在受信任客户端上下文中执行的JavaScript应用程序的作者发起的攻击情况。客户端本身可以与服务器建立联系,并通过 |Origin| 头字段的机制,确定是否将通信权限扩展到该JavaScript应用程序。这一设计的意图并不是阻止非浏览器建立连接,而是确保在可能包含恶意JavaScript的受信任浏览器控制下,不能伪造WebSocket握手。

10.3. 对基础设施的攻击(掩码)

除了端点成为通过WebSocket进行的攻击目标外,网络基础设施的其他部分,如代理,也可能成为攻击的目标。

在开发此协议时,进行了一项实验,以演示一类对代理的攻击,这导致了在实际环境中部署的缓存代理中毒[TALKING]。这种攻击的一般形式是,建立与攻击者控制下的服务器的连接,对HTTP连接执行UPGRADE操作(类似于WebSocket协议建立连接的方式),然后通过UPGRADE后的连接发送看似对特定已知资源(在攻击中可能是广泛部署的用于跟踪点击的脚本或广告服务网络上的资源)的GET请求。远程服务器会响应看似是对虚假GET请求的响应,这个响应会被非零比例的已部署中间代理缓存,从而导致缓存中毒。这种攻击的总体效果是,如果攻击者能够诱使用户访问其控制的网站,攻击者就有可能对该用户以及同一缓存后面的其他用户的缓存进行中毒,并在其他来源上运行恶意脚本,从而破坏Web安全模型。

为了避免对已部署的中间件进行此类攻击,仅仅通过在应用程序提供的数据前添加不符合HTTP规范的帧格式是不够的,因为不可能穷尽地发现和测试每个不符合规范的中间件是否会跳过这种非HTTP帧格式并对帧负载进行错误处理。因此,采用的防御措施是对从客户端发送到服务器的所有数据进行掩码处理,这样远程脚本(攻击者)就无法控制发送的数据在传输线上的表现形式,从而无法构造可能被中间件错误解释为HTTP请求的消息。

客户端必须为每个帧选择一个新的掩码密钥,使用一种无法被提供数据的终端应用程序预测的算法。例如,每个掩码都可以从密码学强随机数生成器中抽取。如果使用了相同的密钥,或者存在可解码的模式来选择下一个密钥,攻击者就可以发送一条消息,当它被掩码后,可能会看起来像是一个HTTP请求(通过取攻击者希望在传输线上看到的消息并用下一个要使用的掩码密钥进行掩码,当客户端应用这个掩码密钥时,它将有效地取消掩码数据)。

此外,一旦客户端的帧传输开始,该帧的有效载荷(应用程序提供的数据)必须不能被应用程序修改。否则,攻击者可以发送一个长帧,其中初始数据是已知值(例如全零),在收到数据的第一部分后计算正在使用的掩码密钥,然后修改帧中尚未发送的数据,使其在掩码后看起来像一个HTTP请求。(这基本上与上一段中描述的使用已知或可预测的掩码密钥的问题相同。)如果需要发送额外数据或待发送数据以某种方式发生更改,则新的或更改的数据必须在新帧中发送,并使用新的掩码密钥。简而言之,一旦帧的传输开始,其内容就不能被远程脚本(应用程序)修改。

所防范的威胁模型是客户端发送看起来像HTTP请求的数据。因此,需要掩码的通道是从客户端到服务器的数据。从服务器到客户端的数据可以看起来像响应,但要完成这个请求,客户端也必须能够伪造请求。因此,不需要在两个方向上都掩码数据(从服务器到客户端的数据不进行掩码)。

尽管掩码提供了保护,但不符合规范的HTTP代理仍然容易受到不应用掩码的客户端和服务器发起的此类中毒攻击。

10.4. 实现特定的限制

那些具有实现和/或平台特定的帧大小或从多个帧重新组装后的总消息大小限制的实现,必须保护自己以免超过这些限制。(例如,恶意端点可以尝试通过发送一个大的单帧(例如,大小为2**60)或发送作为分段消息一部分的小帧长流来耗尽其对等方的内存或发起拒绝服务攻击。)这样的实现应该对帧大小和从多个帧重新组装后的总消息大小施加限制。

10.5. WebSocket客户端认证

本协议不规定服务器在WebSocket握手期间可以使用的任何特定方式来认证客户端。WebSocket服务器可以使用任何通用的HTTP服务器可用的客户端认证机制,例如cookie、HTTP认证或TLS认证。

10.6. 连接保密性和完整性

通过在TLS(wss URI)上运行WebSocket协议来提供连接的保密性和完整性。WebSocket实现必须支持TLS,并且在与对等方通信时应该使用它。

对于使用TLS的连接,TLS提供的益处量很大程度上取决于TLS握手期间协商的算法强度。例如,一些TLS密码机制不提供连接保密性。为了达到合理的保护水平,客户端应该仅使用强TLS算法。“Web安全上下文:用户界面指南”[W3C.REC-wsc-ui-20100812]讨论了构成强TLS算法的要素。[RFC5246]在附录A.5和附录D.3中提供了额外的指导。

10.7. 处理无效数据

客户端和服务器都必须始终验证传入的数据。如果端点在任何时候遇到不理解的数据或违反其确定输入安全性的某些标准的数据,或者当端点看到不符合其预期值的握手(例如,客户端请求中的路径或来源不正确),端点可以断开TCP连接。如果无效数据是在成功的WebSocket握手之后接收的,端点在继续_关闭WebSocket连接_之前应该发送一个带有适当状态码的Close帧(第7.4节)。使用带有适当状态码的Close帧有助于诊断问题。如果无效数据是在WebSocket握手期间发送的,服务器应该返回适当的HTTP [RFC2616]状态码。

当使用错误的编码发送文本数据时,会出现一类常见的安全问题。本协议规定,带有文本数据类型的消息(与二进制或其他类型相对)包含UTF-8编码的数据。虽然长度仍然被指示,并且实现此协议的应用程序应该使用长度来确定帧的实际结束位置,但以不适当的编码发送数据仍然可能破坏基于此协议构建的应用程序可能做出的假设,从而导致从数据的误解释到数据丢失或潜在的安全漏洞等任何问题。

10.8. WebSocket握手对SHA-1的使用

本文档中描述的WebSocket握手不依赖于SHA-1的任何安全属性,如抗碰撞性或抗第二原像攻击(如[RFC4270]所述)。

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编码,然后使用URI [RFC3986]和国际化资源标识符(IRI) [RFC3987]规范中定义的百分号编码形式替换其对应的字节,以从Unicode转换为ASCII。

使用该URI方案名称的应用程序/协议

WebSocket协议

互操作性考虑因素

使用WebSocket要求使用HTTP版本1.1或更高版本。

安全考虑因素

参见“安全考虑因素”部分。

联系人

HYBI工作组 [email protected]

作者/更改控制者

IETF [email protected]

引用

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]中描述的含义。

URI方案语义

该方案的唯一操作是使用WebSocket协议(通过TLS加密)打开连接。

编码考虑因素

主机组件中由上述语法排除的字符必须按照[RFC3987]或其替代方案的规定从Unicode转换为ASCII。为了基于方案的规范化,主机组件的国际化域名(IDN)形式及其转换为punycode被视为等效(参见[RFC3987]的第5.3.3节)。

其他组件中由上述语法排除的字符必须先使用UTF-8编码,然后使用URI [RFC3986]和IRI [RFC3987]规范中定义的百分号编码形式替换其对应的字节,以从Unicode转换为ASCII。

使用该URI方案名称的应用程序/协议

通过TLS的WebSocket协议

互操作性考虑

WebSocket的使用要求使用HTTP 1.1或更高版本。

安全性考虑

请参阅“安全性考虑”部分。

联系人

HYBI工作组 [email protected]

作者/变更控制者

IETF [email protected]

参考

RFC 6455

11.2. "WebSocket" HTTP升级关键字的注册

本节定义了一个在HTTP升级令牌注册表中注册的关键字,如RFC 2817 [RFC2817]所述。

令牌名称

WebSocket

作者/变更控制者

IETF [email protected]

联系人

HYBI [email protected]

参考

RFC 6455

11.3. 新HTTP头部字段的注册

11.3.1. Sec-WebSocket-Key

本节描述了在永久消息头部字段名称注册表[RFC3864]中注册的头部字段。

头部字段名称

Sec-WebSocket-Key

适用协议

http

状态

标准

作者/变更控制者

IETF

规范文档

RFC 6455

相关信息

此头部字段仅用于WebSocket打开握手。

在WebSocket打开握手中使用|Sec-WebSocket-Key|头部字段。它从客户端发送到服务器,提供服务器用于证明它接收到了有效的WebSocket打开握手的一部分信息。这有助于确保服务器不接受来自非WebSocket客户端(例如,HTTP客户端)的连接,这些客户端被滥用以向毫无戒心的WebSocket服务器发送数据。

在HTTP请求中,|Sec-WebSocket-Key|头部字段必须不多于一次。

11.3.2. Sec-WebSocket-Extensions

本节描述了在永久消息头部字段名称注册表[RFC3864]中注册的头部字段。

头部字段名称

Sec-WebSocket-Extensions

适用协议

http

状态

标准

作者/变更控制者

IETF

规范文档

RFC 6455

相关信息

此头部字段仅用于WebSocket打开握手。

|Sec-WebSocket-Extensions|头部字段用于WebSocket打开握手。它最初从客户端发送到服务器,随后从服务器发送到客户端,以就一组在连接期间使用的协议级扩展达成一致。

|Sec-WebSocket-Extensions|头部字段可以在HTTP请求中出现多次(这在逻辑上等同于一个包含所有值的单个|Sec-WebSocket-Extensions|头部字段)。但是,|Sec-WebSocket-Extensions|头部字段在HTTP响应中不得出现多次。

11.3.3. Sec-WebSocket-Accept

本节描述了在永久消息头部字段名称注册表[RFC3864]中注册的头部字段。

头部字段名称

Sec-WebSocket-Accept

适用协议

http

状态

标准

作者/变更控制者

IETF

规范文档

RFC 6455

相关信息

此头部字段仅用于WebSocket打开握手。

|Sec-WebSocket-Accept|头部字段用于WebSocket打开握手。它从服务器发送到客户端,以确认服务器愿意启动WebSocket连接。

|Sec-WebSocket-Accept|头部字段在HTTP响应中不得出现多次。

11.3.4. Sec-WebSocket-Protocol

本节描述了在永久消息头部字段名称注册表[RFC3864]中注册的头部字段。

头部字段名称

Sec-WebSocket-Protocol

适用协议

http

状态

标准

作者/变更控制者

IETF

规范文档

RFC 6455

相关信息

此头部字段仅用于WebSocket打开握手。

|Sec-WebSocket-Protocol|头部字段在WebSocket打开握手中使用。它从客户端发送到服务器,并从服务器返回客户端,以确认连接的子协议。这使得脚本可以选择一个子协议,并确信服务器同意为该子协议提供服务。

|Sec-WebSocket-Protocol|头部字段可以在HTTP请求中出现多次(这在逻辑上等同于一个包含所有值的单个|Sec-WebSocket-Protocol|头部字段)。但是,|Sec-WebSocket-Protocol|头部字段在HTTP响应中不得出现多次。

11.3.5. Sec-WebSocket-Version

本节描述了在永久消息头部字段名称注册表[RFC3864]中注册的头部字段。

头部字段名称

Sec-WebSocket-Version

适用协议

http

状态

标准

作者/变更控制者

IETF

规范文档

RFC 6455

相关信息

此头部字段仅用于WebSocket打开握手。

|Sec-WebSocket-Version|头部字段在WebSocket打开握手中使用。它从客户端发送到服务器,以指示连接的协议版本。这使得服务器能够正确地解释打开握手以及后续从客户端发送的数据,并在服务器无法以安全方式解释这些数据时关闭连接。当从客户端接收到的版本与服务器理解的版本不匹配时,|Sec-WebSocket-Version|头部字段也会在WebSocket握手错误时从服务器发送到客户端。在这种情况下,头部字段将包含服务器支持的协议版本。

请注意,并不期望更高的版本号必然与较低的版本号向后兼容。|Sec-WebSocket-Version|头部字段可以在HTTP响应中出现多次(这在逻辑上等同于一个包含所有值的单个|Sec-WebSocket-Version|头部字段)。但是,|Sec-WebSocket-Version|头部字段在HTTP请求中不得出现多次。

11.4. WebSocket 扩展名称注册表

本规范为WebSocket扩展名称创建了一个新的IANA注册表,该注册表将用于WebSocket协议,符合RFC 5226 [RFC5226]中阐述的原则。

作为该注册表的一部分,IANA维护以下信息:

扩展标识符

扩展的标识符,如本规范第11.3.2节中注册的|Sec-WebSocket-Extensions|头部字段中所使用的。该值必须符合本规范第9.1节中定义的extension-token的要求。

扩展通用名称

扩展的名称,即通常所指的扩展名。

扩展定义

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

已知不兼容的扩展

一个扩展标识符列表,这些扩展标识符与本扩展已知不兼容。

WebSocket扩展名称将遵循IANA的“先到先得”注册政策[RFC5226]。

该注册表中没有初始值。

11.5. WebSocket 子协议名称注册表

本规范为WebSocket子协议名称创建了一个新的IANA注册表,该注册表将用于WebSocket协议,符合RFC 5226 [RFC5226]中阐述的原则。

作为该注册表的一部分,IANA维护以下信息:

子协议标识符

子协议的标识符,如本规范第11.3.4节中注册的|Sec-WebSocket-Protocol|头部字段中所使用的。该值必须符合本规范第4.1节第10项给出的要求——即,该值必须是一个由RFC 2616 [RFC2616]定义的标记(token)。

子协议通用名称

子协议的名称,即通常所指的子协议名。

子协议定义

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

WebSocket子协议名称将遵循IANA的“先到先得”注册政策[RFC5226]。

11.6. WebSocket 版本号注册表

本规范为WebSocket版本号创建了一个新的IANA注册表,该注册表将用于WebSocket协议,符合RFC 5226 [RFC5226]中阐述的原则。

作为该注册表的一部分,IANA维护以下信息:

版本号

在|Sec-WebSocket-Version|中使用的版本号在本规范的第4.1节中指定。该值必须是在0到255(含)范围内的非负整数。

引用

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

状态

“Interim(临时)”或“Standard(标准)”。见下文描述。

版本号被指定为“Interim(临时)”或“Standard(标准)”。

“Standard(标准)”版本号在RFC中记录,用于标识WebSocket协议的主要稳定版本,例如本RFC所定义的版本。“Standard(标准)”版本号需遵守“IETF Review”IANA注册政策[RFC5226]。

“Interim(临时)”版本号记录在Internet-Draft中,用于帮助实现者识别并与已部署的WebSocket协议版本进行互操作,例如在本RFC发布之前开发的版本。“Interim(临时)”版本号需遵守“Expert Review(专家评审)”IANA注册政策[RFC5226],HYBI工作组的主席(或如果工作组关闭,则由IETF应用区域的区域主任)作为初始的指定专家。

IANA已在该注册表中添加了以下初始值。

+--------+-----------------------------------------+----------+

| 版本号 | 引用 | 状态 |

+--------+-----------------------------------------+----------+

| 0 | draft-ietf-hybi-thewebsocketprotocol-00 | 临时 |

+--------+-----------------------------------------+----------+

| 1 | draft-ietf-hybi-thewebsocketprotocol-01 | 临时 |

+--------+-----------------------------------------+----------+

| 2 | draft-ietf-hybi-thewebsocketprotocol-02 | 临时 |

+--------+-----------------------------------------+----------+

| 3 | draft-ietf-hybi-thewebsocketprotocol-03 | 临时 |

+--------+-----------------------------------------+----------+

| 4 | draft-ietf-hybi-thewebsocketprotocol-04 | 临时 |

+--------+-----------------------------------------+----------+

| 5 | draft-ietf-hybi-thewebsocketprotocol-05 | 临时 |

+--------+-----------------------------------------+----------+

| 6 | draft-ietf-hybi-thewebsocketprotocol-06 | 临时 |

+--------+-----------------------------------------+----------+

| 7 | draft-ietf-hybi-thewebsocketprotocol-07 | 临时 |

+--------+-----------------------------------------+----------+

| 8 | draft-ietf-hybi-thewebsocketprotocol-08 | 临时 |

+--------+-----------------------------------------+----------+

| 9 | 保留 | |

+--------+-----------------------------------------+----------+

| 10 | 保留 | |

+--------+-----------------------------------------+----------+

| 11 | 保留 | |

+--------+-----------------------------------------+----------+

| 12 | 保留 | |

+--------+-----------------------------------------+----------+

| 13 | RFC 6455 | 标准 |

+--------+-----------------------------------------+----------+

11.7. WebSocket 关闭代码编号注册表

本规范根据 RFC 5226 [RFC5226] 中所述的原则,为 WebSocket 连接关闭代码编号创建了一个新的 IANA 注册表。

作为该注册表的一部分,IANA 维护以下信息:

状态码

状态码表示根据本文档第 7.4 节规定的 WebSocket 连接关闭的原因。状态码是一个介于 1000 和 4999(包括 1000 和 4999)之间的整数。

含义

状态码的含义。每个状态码必须具有唯一的含义。

联系人

为保留状态码的实体提供的联系人信息。

引用

请求状态码并定义其含义的稳定文档。这对于范围在 1000-2999 内的状态码是必需的,对于范围在 3000-3999 内的状态码是建议的。

WebSocket 关闭代码编号根据其范围的不同,需要遵守不同的注册要求。用于本协议及其后续版本或扩展的状态码请求,应遵守 IANA 注册的“标准行动”、“需要规范”(这暗示了“指定专家”)或“IESG 审查”中的任何一个政策,并且应在 1000-2999 的范围内授予。用于库、框架和应用程序的状态码请求,应遵守 IANA 注册的“先到先得”政策,并应在 3000-3999 的范围内授予。状态码范围 4000-4999 专用于私人使用。请求应指明它们是请求用于 WebSocket 协议(或该协议的未来版本)、扩展,还是库/框架/应用程序的状态码。

IANA 已在注册表中添加了以下初始值。

状态码    含义       联系人    引用

1000       正常关闭       [email protected] RFC 6455

1001       离开       [email protected] RFC 6455

1002       协议错误       [email protected] RFC 6455

1003       不支持的数据       [email protected] RFC 6455

1004       ---保留------     [email protected] RFC 6455

1005       未收到状态    [email protected] RFC 6455

1006       异常关闭       [email protected] RFC 6455

1007       无效帧    [email protected] RFC 6455

负载数据             

1008       违反策略       [email protected] RFC 6455

1009       消息过大       [email protected] RFC 6455

1010       强制扩展       [email protected] RFC 6455

1011       内部服务器错误    [email protected] RFC 6455

1015       TLS 握手错误              [email protected] RFC 6455

11.8. WebSocket 操作码注册表

本规范根据 RFC 5226 [RFC5226] 中规定的原则,为 WebSocket 操作码创建了一个新的 IANA 注册表。

作为这个注册表的一部分,IANA 维护以下信息:

操作码

操作码表示 WebSocket 帧的帧类型,如第 5.2 节所定义。操作码是一个介于 0 到 15(包括 0 和 15)之间的整数。

含义

操作码值的含义。

引用

请求该操作码的规范。

WebSocket 操作码号码遵循 IANA 的“标准行动”注册政策 [RFC5226]。

IANA 已向注册表添加了以下初始值。

操作码    含义       引用

0     延续帧(Continuation Frame)  RFC 6455

1     文本帧(Text Frame) RFC 6455

2     二进制帧(Binary Frame) RFC 6455

8     连接关闭帧(Connection Close Frame)  RFC 6455

9     Ping 帧  RFC 6455

10    Pong 帧 RFC 6455

11.9. WebSocket 帧头位元注册表

本规范根据 RFC 5226 [RFC5226] 中规定的原则,为 WebSocket 帧头位元创建了一个新的 IANA 注册表。该注册表控制第 5.2 节中标记为 RSV1、RSV2 和 RSV3 的位元的分配。

这些位元是为本规范的未来版本或扩展预留的。

WebSocket 帧头位元的分配遵循 IANA 的“标准行动”注册政策 [RFC5226]。

从其他规范中使用 WebSocket 协议

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

这样的规范首先需要 建立 WebSocket 连接,并为该算法提供以下信息:

目标地址,由 /host/ 和 /port/ 组成。

/资源名称/,允许在单个主机和端口上识别多个服务。

/secure/ 标志,如果连接需要加密则为真,否则为假。

负责连接的来源的 ASCII 序列化 [RFC6454]。

可选地,一个字符串,用于标识要在 WebSocket 连接上分层的协议。

/host/、/port/、/资源名称/ 和 /secure/ 标志通常通过解析 WebSocket URI 的组件步骤从 URI 中获得。如果 URI 未指定 WebSocket,则这些步骤将失败。

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

第 7.1.4 节定义了 WebSocket 连接何时关闭。

当连接处于打开状态时,规范需要处理 接收到 WebSocket 消息(第 6.2 节)的情况。

要向已打开的连接发送一些数据 /data/,规范需要 发送 WebSocket 消息(第 6.1 节)。

致谢

特别感谢 Ian Hickson,他是本协议的原始作者和编辑。本规范的初始设计得益于 WHATWG(Web 超文本应用技术工作组)和 WHATWG 邮件列表中许多人的参与。该规范的贡献并没有按部分进行跟踪,但在 WHATWG HTML 规范中列出了所有为该规范做出贡献的人员名单,网址为 http://whatwg.org/html5。

还要特别感谢 John Tamplin 为本规范的“数据帧”部分提供了大量文本。

同样要特别感谢 Adam Barth 为本规范的“数据掩码”部分提供了大量文本和背景研究。

特别感谢 Lisa Dusseault 进行的应用区域审查(以及帮助启动这项工作),Richard Barnes 进行的 Gen-Art 审查,以及 Magnus Westerlund 进行的传输区域审查。特别感谢 HYBI 工作组过去和现在的工作组主席 Joe Hildebrand、Salvatore Loreto 和 Gabriel Montenegro,他们在幕后不懈工作,推动这项工作走向完成。最后但同样重要的是,特别感谢负责的区域主任 Peter Saint-Andre。

感谢以下参与 HYBI 工作组邮件列表讨论、贡献想法和/或提供详细审查的人员(名单可能不完整):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]

美国国家标准学会,“编码字符集 - 7位美国信息交换标准代码”,ANSI X3.4,1986年。

[FIPS.180-3]

美国国家标准技术研究所,“安全哈希标准”,FIPS PUB 180-3,2008年10月,http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf。

[RFC1928] Leech, M., Ganis, M., Lee, Y., Kuris, R., Koblas, D., 和 L. Jones, “SOCKS协议版本5”,RFC 1928,1996年3月。

[RFC2119] Bradner, S., “在RFC中使用以指示要求级别的关键词”,BCP 14,RFC 2119,1997年3月。

[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., 和 T. Berners-Lee, “超文本传输协议 - HTTP/1.1”,RFC 2616,1999年6月。

[RFC2817] Khare, R. 和 S. Lawrence, “在HTTP/1.1中升级到TLS”,RFC 2817,2000年5月。

[RFC2818] Rescorla, E., “HTTP over TLS”,RFC 2818,2000年5月。

[RFC3629] Yergeau, F., “UTF-8,ISO 10646的一种转换格式”,STD 63,RFC 3629,2003年11月。

[RFC3864] Klyne, G., Nottingham, M., 和 J. Mogul, “消息头字段注册流程”,BCP 90,RFC 3864,2004年9月。

[RFC3986] Berners-Lee, T., Fielding, R., 和 L. Masinter, “统一资源标识符(URI):通用语法”,STD 66,RFC 3986,2005年1月。

[RFC3987] Duerst, M. 和 M. Suignard, “国际化资源标识符(IRIs)”,RFC 3987,2005年1月。

[RFC4086] Eastlake, D., Schiller, J., 和 S. Crocker, “安全性的随机性要求”,BCP 106,RFC 4086,2005年6月。

[RFC4648] Josefsson, S., “Base16、Base32和Base64数据编码”,RFC 4648,2006年10月。

[RFC5226] Narten, T. 和 H. Alvestrand, “RFC中编写IANA注意事项部分的指南”,BCP 26,RFC 5226,2008年5月。

[RFC5234] Crocker, D. 和 P. Overell, “语法规范用的扩展BNF:ABNF”,STD 68,RFC 5234,2008年1月。

[RFC5246] Dierks, T. 和 E. Rescorla, “传输层安全性(TLS)协议版本1.2”,RFC 5246,2008年8月。

[RFC6066] Eastlake, D., “传输层安全性(TLS)扩展:扩展定义”,RFC 6066,2011年1月。

[RFC6454] Barth, A., “Web源概念”,RFC 6454,2011年12月。

14.2. 提供性参考文件

[RFC4122] Leach, P., Mealling, M., 和 R. Salz, “通用唯一标识符(UUID)URN命名空间”,RFC 4122,2005年7月。

[RFC4270] Hoffman, P. 和 B. Schneier, “互联网协议中密码哈希的攻击”,RFC 4270,2005年11月。

[RFC5321] Klensin, J., “简单邮件传输协议”,RFC 5321,2008年10月。

[RFC6202] Loreto, S., Saint-Andre, P., Salsano, S., 和 G. Wilkins, “双向HTTP中使用长轮询和流传输的已知问题和最佳实践”,RFC 6202,2011年4月。

[RFC6265] Barth, A., “HTTP状态管理机制”,RFC 6265,2011年4月。

[TALKING] Huang, L-S., Chen, E., Barth, A., Rescorla, E., 和 C. Jackson, “为了乐趣和利益与自己对话”,2010年,http://w2spconf.com/2011/papers/websocket.pdf。

[W3C.REC-wsc-ui-20100812]

Roessler, T. 和 A. Saldhana, “Web安全上下文:用户界面指南”,万维网联盟推荐标准REC-wsc-ui-20100812,2010年8月,http://www.w3.org/TR/2010/REC-wsc-ui-20100812/。

最新版本可在以下网址获取:<http://www.w3.org/TR/wsc-ui/>。

[WSAPI] Hickson, I., “WebSocket API”,W3C工作草案WD-websockets-20110929,2011年9月,http://www.w3.org/TR/2011/WD-websockets-20110929/。

最新版本可在以下网址获取:<http://www.w3.org/TR/websockets/>。

[XMLHttpRequest]

van Kesteren, A.(主编),“XMLHttpRequest”,W3C候选推荐标准CR-XMLHttpRequest-20100803,2010年8月,http://www.w3.org/TR/2010/CR-XMLHttpRequest-20100803/。

最新版本可在以下网址获取:http://www.w3.org/TR/XMLHttpRequest/。

作者地址:

Ian Fette

Google, Inc.

电子邮件:mailto:[email protected]

网址:http://www.ianfette.com/

Alexey Melnikov

Isode Ltd.

5 Castle Business Village

36 Station Road

Hampton, Middlesex TW12 2BX

英国

电子邮件:mailto:[email protected]

标签:websocket,Sec,--,握手,WebSocket,服务器,中文翻译,连接,客户端
From: https://blog.csdn.net/zhouwuhua/article/details/139468396

相关文章

  • OpenAI的Sam Altman搞核聚变了?!究竟是创新还是疯狂?|TodayAI
    据《华尔街日报》报道,西雅图地区的核聚变公司HelionEnergy正在与人工智能公司OpenAI洽谈一项重要交易,OpenAI计划“购买大量电力为数据中心提供动力”。这一消息引起了广泛关注。OpenAI的首席执行官兼联合创始人SamAltman已向Helion投资了3.75亿美元,并担任该公司的董事会主......
  • 基于单片机的超声波倒车雷达设计
    摘要:文章设计了一种基于单片机的超声波倒车雷达系统,以AT89C51型单片机作为控制核心,集距离测量、显示,方位显示和危险报警于一体,以提高驾驶者在倒车泊车时的安全性和舒适性。本设计采用Keil软件对系统程序进行调试,并采用Proteus对整个系统进行了仿真,仿真结果达到了......
  • 控制台警告:[Violation] Added non-passive event listener to a scroll-blocking 'mou
    控制台警告:[Violation]Addednon-passiveeventlistenertoascroll-blocking'mousewheel'event.Considermarkingeventhandleras'passive'tomakethepagemoreresponsive.Seehttps://www.chromestatus.com/feature/5745543795965952[Viola......
  • 基于单片机的电子万年历设计
    摘要:本设计以AT89C51单片机为主控器,使用DS1302时钟芯片、DS18B20温度芯片、LCD1602显示模块,利用Proteus仿真软件和Keil编译软件进行了基于单片机的电子万年历仿真,设计的万年历可以在液晶上显示时间,同时还具有时间校准、温度显示等功能。关键词:单片机;万年历;......
  • C# 12 new feature Collection Expression,Primary Consctructor,Generic type alias,
    usinglistOfInt=System.Collections.Generic.List<int>;namespaceConsoleApp36{internalclassProgram{staticvoidMain(string[]args){GenericTypeAlias();}staticvoidGenericTypeAlias()......
  • Oracle 表内数据量少,但是查询速度很慢
    优化方向1.使用合适的索引:确保查询中涉及的字段有适当的索引。索引可以帮助数据库引擎快速定位和检索数据,提高查询效率。2.避免使用通配符查询:尽量避免在查询条件中使用通配符'%',因为这样的查询会导致全表扫描,影响性能。3.避免使用函数:在查询条件中避免使用函数,尽量在字段上......
  • 从事网络安全等保测评,这个证大学生也可以考!
    在数字化时代的浪潮中,网络安全问题日益凸显,成为了各行各业不可忽视的重要议题。作为保障网络安全的重要一环,等保测评工作也逐渐受到了广泛的关注。而令人欣喜的是,如今,大学生们也有机会参与到这一领域中,通过考取网络安全等保测评证书,为自己的未来增添一份独特的竞争力。首先......
  • StarCCM指定无限制的并行度
    在使用StarCCM+进行批处理计算时,如果您希望指定无限制的并行度(即使用所有可用的计算资源),可以通过修改批处理脚本来实现。以下是一个简化的批处理脚本示例,它设置了无限制的并行度:bash#!/bin/bash#设置StarCCM+的环境变量exportSTARCCM_ROOT=/path/to/starccm_direxportSTARCCM......
  • 巨量千川新手投放教程
    巨量千川是抖音广告投放的全新统合,是抖音广告产品升级后的全新形态,能更好地满足不同类型商家的投放需求。对于新手来说,了解和掌握巨量千川的投放技巧至关重要。本文将为你提供一份详细的巨量千川新手投放教程,帮助你在广告投放中取得更好的效果。一、了解巨量千川在开始投......
  • BOM是什么
    BOM(BrowserObjectModel)#一、是什么BOM (BrowserObjectModel),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏......