1. 介绍
本节为非规范性内容。
为了使Web应用程序能够与服务器端进程保持双向通信,本规范引入了WebSocket
接口。
此接口不允许对底层网络进行原始访问。例如,不能使用此接口来实现一个IRC客户端,而不通过自定义服务器代理消息。
2. WebSocket 协议变更
WebSocket
API
使用了这种语言。[WSP] [FETCH]
其工作原理是通过用一个与 Fetch 集成的新算法替换 WebSocket 协议的“建立 WebSocket 连接”算法。“建立 WebSocket 连接”由三个算法组成:设置连接、创建并传输握手请求,以及验证握手响应。这种分层与 Fetch 不同,后者首先创建握手,然后设置连接并传输握手,最后验证响应。在阅读这些更改时请牢记这一点。
2.1. 连接
为了获取一个 WebSocket 连接,给定一个 url,执行以下步骤:
-
令host为url的host。
-
令port为url的端口。
-
令资源名称为 U+002F (/),后跟url的路径中的字符串(包括空字符串,如果有的话),字符串之间用 U+002F (/) 分隔。
-
令secure为 false,如果url的scheme为 "
http
";否则为 true。 -
按照 The WebSocket Protocol 的第 4.1 节中第一个步骤集合中的第 2 至 5 步的要求,建立一个WebSocket 连接,传递 host、port、资源名称和secure。[WSP]
-
如果建立了连接,则返回它,否则返回失败。
尽管结构略有不同,属性不同,因此不可共享,但 WebSocket 连接与“普通”连接非常接近。
2.2. 开启握手
为了建立 一个 WebSocket 连接,给定一个url、protocols和client,执行以下步骤:
-
令requestURL为url的副本,如果url的scheme是"
ws
",则将其scheme设置为"http
",否则设置为"https
"。这种 scheme 的更改对于与fetch的良好集成至关重要。例如,没有它,HSTS 将无法工作。实际上,WebSocket 没有独特的 scheme 是没有实际理由的,这是一个遗留的产物。[HSTS]
-
令request为一个新的请求,其URL为requestURL,客户端为client,service-workers 模式为"
none
", referrer为"no-referrer
",模式为"websocket
",凭据模式为"include
",缓存模式为"no-store
", 并且重定向模式为"error
"。 -
令keyValue为一个随机选择的 16 字节值的 nonce,已宽容的 Base64 编码并同构编码。
如果随机选择的值是字节序列 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,keyValue将被宽容的 Base64 编码为"
AQIDBAUGBwgJCgsMDQ4PEC==
"并同构编码为`AQIDBAUGBwgJCgsMDQ4PEC==
`。 -
对于protocols中的每一个protocol,合并(`
Sec-WebSocket-Protocol
`,protocol)到request的header 列表。 -
令permessageDeflate为用户代理定义的"
permessage-deflate
"扩展header 值。[WSP] -
追加 (`
Sec-WebSocket-Extensions
`,permessageDeflate)到request的header 列表。 -
Fetch request,并将useParallelQueue设置为 true, 并给予processResponse这些步骤,其中response为:
-
如果response是网络错误或其状态不是 101,WebSocket 连接失败。
-
如果protocols不是空列表,并且提取 header 列表值时,给定 `
Sec-WebSocket-Protocol
` 和 response的header 列表结果为 null、失败或空字节序列,则WebSocket 连接失败。这与 The WebSocket Protocol 中定义的对该 header 的检查不同。后者仅涵盖客户端未请求的子协议。而本条款涵盖客户端请求但服务器未确认的子协议。
-
按照 The WebSocket Protocol 中的第 4.1 节中最后一个步骤集合中的第 2 至 6 步的要求,验证response。这将导致WebSocket 连接失败或WebSocket 连接已建立。
-
WebSocket 连接失败和WebSocket 连接已建立由 The WebSocket Protocol 定义。[WSP]
不跟随重定向且此握手通常受限的原因是,因为在 Web 浏览器环境中可能引入严重的安全问题。例如,考虑一个在某一路径上有 WebSocket 服务器的 host,并且在另一路径上有开放的 HTTP 重定向器。突然之间,任何可以给定特定 WebSocket URL 的脚本都可能被欺骗与互联网上的任何 host 通信(甚至可能共享机密信息),即使该脚本检查了 URL 是否具有正确的主机名。
3. WebSocket
接口
3.1. 接口定义
WebSocket
类的
Web IDL 定义如下所示:
enum {
BinaryType "blob" ,"arraybuffer" }; [Exposed =(Window ,Worker )]interface :
WebSocket EventTarget {constructor (USVString ,
url optional (DOMString or sequence <DOMString >)= []);
protocols readonly attribute USVString url ; // ready stateconst unsigned short CONNECTING = 0;const unsigned short OPEN = 1;const unsigned short CLOSING = 2;const unsigned short CLOSED = 3;readonly attribute unsigned short readyState ;readonly attribute unsigned long long bufferedAmount ; // networkingattribute EventHandler onopen ;attribute EventHandler onerror ;attribute EventHandler onclose ;readonly attribute DOMString extensions ;readonly attribute DOMString protocol ;undefined close (optional [Clamp ]unsigned short ,
code optional USVString ); // messaging
reason attribute EventHandler onmessage ;attribute BinaryType binaryType ;undefined send ((BufferSource or Blob or USVString )); };
data
每个WebSocket
对象都有一个关联的url,
它是一个URL
记录。
每个WebSocket
对象都有一个关联的binary type,
它是一个BinaryType
。它的初始值必须为"blob
"。
每个WebSocket
对象都有一个关联的ready state,它是表示连接状态的数字。初始状态必须为CONNECTING
(0)。
它可以具有以下值:
CONNECTING
(数值 0)-
连接尚未建立。
OPEN
(数值 1)-
WebSocket 连接已建立并且可以进行通信。
CLOSING
(数值 2)-
连接正在进行关闭握手,或已调用
close()
方法。 CLOSED
(数值 3)-
连接已关闭或无法打开。
-
socket = new
WebSocket
(url [, protocols ]) -
创建一个新的
WebSocket
对象,并立即建立关联的 WebSocket 连接。url 是一个字符串,指定建立连接的URL。 仅允许 "
ws
"、"wss
"、"http
" 和 "https
" scheme;其他 scheme 将导致 "SyntaxError
"DOMException
。 带有片段的 URL 总会导致此类异常。protocols 是一个字符串或字符串数组。如果它是一个字符串,则相当于仅包含该字符串的数组;如果省略,则相当于空数组。 数组中的每个字符串都是一个子协议名称。仅当服务器报告已选择这些子协议之一时,连接才会建立。子协议名称必须符合 The WebSocket protocol 中定义的 `
Sec-WebSocket-Protocol
` 字段的值组成元素的要求。[WSP] -
socket.send(data)
-
使用 WebSocket 连接传输data。data可以是一个字符串,一个
Blob
, 一个ArrayBuffer
, 或一个ArrayBufferView
。 -
socket.close([ code ] [, reason ])
-
关闭 WebSocket 连接,可选地使用code作为WebSocket 连接关闭代码和reason作为WebSocket 连接关闭原因。
-
socket.url
-
返回用于建立 WebSocket 连接的URL。
-
socket.readyState
-
返回 WebSocket 连接的状态。它可以具有上述值。
-
socket.bufferedAmount
-
返回已通过
send()
排队但尚未传输到网络的应用程序数据(UTF-8 文本和二进制数据)的字节数。如果 WebSocket 连接已关闭,此属性的值将仅在每次调用
send()
方法时增加。(连接关闭后,该数字不会重置为零。) -
socket.extensions
-
返回服务器选择的扩展(如果有)。
-
socket.protocol
-
返回服务器选择的子协议(如果有)。它可以与构造函数第二个参数的数组形式一起使用,以执行子协议协商。
-
socket.binaryType
-
返回一个字符串,指示从socket中获取的二进制数据如何暴露给脚本:
- "
blob
" -
二进制数据以
Blob
形式返回。 - "
arraybuffer
" -
二进制数据以
ArrayBuffer
形式返回。
默认值为"
blob
"。 - "
-
socket.binaryType = value
-
更改二进制数据的返回方式。
new WebSocket(url, protocols)
构造函数步骤如下:
-
令baseURL为this的相关设置对象的API 基础 URL。
-
令urlRecord为通过将url与baseURL一起应用URL 解析器后的结果。
-
如果urlRecord失败,则抛出"
SyntaxError
"DOMException
。 -
如果urlRecord的scheme不是"
ws
" 或"wss
", 则抛出"SyntaxError
"DOMException
。 -
如果urlRecord的片段不为 null,则抛出"
SyntaxError
"DOMException
。 -
如果protocols是一个字符串,则将protocols设置为仅包含该字符串的序列。
-
如果protocols中的任何值出现多次或以其他方式不符合 The WebSocket protocol 中定义的 `
Sec-WebSocket-Protocol
` 字段的值组成元素的要求, 则抛出"SyntaxError
"DOMException
。 [WSP] -
并行执行以下步骤:
-
给定urlRecord、protocols和client,建立 WebSocket 连接。[FETCH]
如果建立 WebSocket 连接算法失败,它会触发WebSocket 连接失败算法, 然后调用关闭 WebSocket 连接算法, 然后确定WebSocket 连接已关闭, 并会触发
close
事件,如下所述。
-
url
的 getter 步骤是返回this的url,并序列化。
readyState
的 getter 步骤是返回this的ready state。
extensions
属性最初必须返回空字符串。在WebSocket
连接建立后,其值可能会发生变化,如下所述。
protocol
属性最初必须返回空字符串。在WebSocket
连接建立后,其值可能会发生变化,如下所述。
close(code, reason)
方法的步骤如下:
-
如果code存在,但既不是等于 1000 的整数也不是在 3000 到 4999 之间(含)的整数,则抛出一个"
InvalidAccessError
"DOMException
。 -
如果reason存在,则执行以下子步骤:
-
令reasonBytes为编码reason的结果。
-
如果reasonBytes超过 123 字节,则抛出一个"
SyntaxError
"DOMException
。
-
-
从以下列表中运行第一个匹配步骤:
- 如果this的ready state是
CLOSING
(2) 或CLOSED
(3) -
什么也不做。
- 如果 WebSocket 连接尚未建立 [WSP]
-
WebSocket 连接失败并将this的ready state设置为
CLOSING
(2)。[WSP]WebSocket 连接失败算法调用关闭 WebSocket 连接算法, 然后确定WebSocket 连接已关闭, 这会触发
close
事件,如下所述。 - 如果 WebSocket 关闭握手尚未开始[WSP]
-
启动 WebSocket 关闭握手并将this的ready state设置为
CLOSING
(2)。[WSP]如果code和reason都不存在,WebSocket Close 消息必须没有正文。
The WebSocket Protocol 错误地指出状态码是启动 WebSocket 关闭握手算法的必需项。
如果code存在,则在 WebSocket Close 消息中使用的状态码必须是code给出的整数。[WSP]
如果reason也存在,则reasonBytes必须在 Close 消息中的状态码之后提供。[WSP]
启动 WebSocket 关闭握手算法最终会调用关闭 WebSocket 连接算法, 然后确定WebSocket 连接已关闭, 这会触发
close
事件,如下所述。 - 否则
-
将this的ready state设置为
CLOSING
(2)。WebSocket 关闭握手已开始,最终会调用关闭 WebSocket 连接算法, 然后确定WebSocket 连接已关闭, 从而会触发
close
事件,如下所述。
- 如果this的ready state是
close()
方法在启动 WebSocket 关闭握手之前不会丢弃之前发送的消息——即使在实际操作中,用户代理仍在忙于发送这些消息,握手也只会在消息发送后开始。
bufferedAmount
的 getter 步骤是返回使用send()
排队的应用程序数据(UTF-8
文本和二进制数据)的字节数,
但截至事件循环上次达到步骤 1时,
这些数据尚未传输到网络。(因此这包括在当前任务执行期间发送的任何文本,无论用户代理是否能够在脚本执行期间并行传输文本。)这不包括协议产生的帧开销,或操作系统或网络硬件的缓冲。
在这个简单的示例中,bufferedAmount
属性用于确保更新发送的频率为每 50 毫秒一次,或者在网络能够处理此频率时,按网络能够处理的速度发送更新。
var socket= new WebSocket( 'ws://game.example.com:12010/updates' ); socket. onopen= function () { setInterval( function () { if ( socket. bufferedAmount== 0 ) socket. send( getUpdateData()); }, 50 ); };
bufferedAmount
属性还可以用于饱和网络,而不会以高于网络能处理的速度发送数据,尽管这需要更仔细地监控属性随时间的变化。
binaryType
的 getter 步骤是返回this的binary type。
binaryType
的 setter 步骤是将this的binary type设置为给定值。
用户代理可以将binary type用作处理传入二进制数据的提示:如果它是"blob
",
则可以安全地将其传输到磁盘;如果它是"arraybuffer
",
则将数据保存在内存中可能更有效率。当然,鼓励用户代理使用更细微的启发式方法来决定是否将传入的数据保存在内存中,例如基于数据的大小或脚本在最后一刻更改属性的频率。这一点尤其重要,因为在用户代理接收到数据之后但在用户代理触发事件之前更改属性是完全可能的。
send(data)
方法的步骤如下:
-
如果this的ready state是
CONNECTING
, 则抛出一个 "InvalidStateError
"DOMException
。 -
从以下列表中运行适当的步骤集:
- 如果data是字符串
-
如果WebSocket 连接已建立并且WebSocket 关闭握手尚未开始, 则用户代理必须发送由data参数组成的 WebSocket 消息,使用文本帧操作码; 如果无法发送数据,例如因为它需要缓冲但缓冲区已满,则用户代理必须将 WebSocket 标记为已满,然后关闭 WebSocket 连接。任何以字符串参数调用此方法且未抛出异常的操作都必须增加
bufferedAmount
属性, 增加的数量为将参数表达为 UTF-8 所需的字节数。[UNICODE] [ENCODING] [WSP] - 如果data是
Blob
对象 -
如果WebSocket 连接已建立,并且WebSocket 关闭握手尚未开始, 则用户代理必须发送由data组成的 WebSocket 消息,使用二进制帧操作码; 如果无法发送数据,例如因为它需要缓冲但缓冲区已满,则用户代理必须将 WebSocket 标记为已满,然后关闭 WebSocket 连接。要发送的数据是
Blob
对象表示的原始数据。 任何以Blob
参数调用此方法且未抛出异常的操作都必须 按照bufferedAmount
属性增加Blob
对象的原始数据大小,以字节为单位。[WSP] [FILEAPI] - 如果data是
ArrayBuffer
-
如果WebSocket 连接已建立,并且WebSocket 关闭握手尚未开始, 则用户代理必须发送由data组成的 WebSocket 消息,使用二进制帧操作码; 如果无法发送数据,例如因为它需要缓冲但缓冲区已满,则用户代理必须将 WebSocket 标记为已满,然后关闭 WebSocket 连接。要发送的数据是缓冲区中
ArrayBuffer
对象描述的数据。 任何以ArrayBuffer
参数调用此方法且未抛出异常的操作都必须 按照bufferedAmount
属性增加ArrayBuffer
的长度(以字节为单位)。[WSP] - 如果data是
ArrayBufferView
-
如果WebSocket 连接已建立,并且WebSocket 关闭握手尚未开始, 则用户代理必须发送由data组成的 WebSocket 消息,使用二进制帧操作码; 如果无法发送数据,例如因为它需要缓冲但缓冲区已满,则用户代理必须将 WebSocket 标记为已满,然后关闭 WebSocket 连接。要发送的数据是
ArrayBuffer
对象中data引用的缓冲区部分的数据。 任何以此类参数调用此方法且未抛出异常的操作都必须按照bufferedAmount
属性 按照data缓冲区的长度(以字节为单位)增加该属性的值。[WSP]
以下是所有实现WebSocket
接口的对象必须支持的事件处理程序(及其对应的事件处理程序事件类型),作为事件处理程序 IDL 属性:
事件处理程序 | 事件处理程序事件类型 |
---|---|
onopen
| open
|
onmessage
| message
|
onerror
| error
|
onclose
| close
|
4. 来自协议的反馈
当WebSocket 连接已建立时,用户代理必须排队一个任务来运行这些步骤:
-
将ready state更改为
OPEN
(1)。 -
如果不是空值,将
extensions
属性的值更改为使用中的扩展。[WSP]
由于上述算法是作为任务排队的,因此在WebSocket
连接已建立与脚本设置open
事件的事件侦听器之间不存在竞争条件。
当接收到类型为type和数据为data的WebSocket 消息时,用户代理必须排队一个任务,以遵循以下步骤:[WSP]
-
如果ready state不是
OPEN
(1),则返回。 -
根据type和binary type的情况来确定dataForEvent:
- type表示数据为文本
-
一个包含data的新
DOMString
- type表示数据为二进制且binary type为
"blob"
- type表示数据为二进制且binary type为
"arraybuffer"
-
一个在
WebSocket
对象的相关领域中创建的新ArrayBuffer
对象, 其内容为data
-
使用
MessageEvent
,触发在WebSocket
对象上的一个名为message
的事件,origin
属性初始化为序列化WebSocket
对象的url的源,data
属性初始化为dataForEvent。
建议用户代理在运行任务之前检查是否可以有效地执行上述步骤,在准备缓冲区时从其他任务队列中挑选任务。
例如,如果当数据到达时binary type为"blob
",
并且用户代理将所有数据缓存到磁盘,但在运行上述特定消息的任务之前脚本将binary
type切换为"arraybuffer
",
则用户代理会希望在运行此任务之前将数据分页回 RAM,以避免在创建ArrayBuffer
对象时阻塞主线程。
下面是如何在文本帧的情况下定义message
事件处理程序的示例:
mysocket. onmessage= function ( event) { if ( event. data== 'on' ) { turnLampOn(); } else if ( event. data== 'off' ) { turnLampOff(); } };
此处的协议很简单,服务器仅发送“on”或“off”消息。
当WebSocket 关闭握手启动时,用户代理必须排队一个任务将ready state更改为CLOSING
(2)。(如果close()
方法
被调用,当此任务运行时,ready
state将已经设置为CLOSING
(2))。[WSP]
当WebSocket 连接关闭时,可能是干净地关闭,用户代理必须排队一个任务来运行以下子步骤:
-
将ready state更改为
CLOSED
(3)。 -
如果用户代理需要WebSocket 连接失败,或者如果WebSocket 连接关闭是在被标记为已满之后发生的,触发一个事件,在
WebSocket
对象上命名为error
的事件。[WSP] -
触发一个事件,命名为
close
,在WebSocket
对象上, 使用CloseEvent
,将wasClean
属性初始化为 true,如果连接 干净地 关闭,否则为 false,并将code
属性初始化为 WebSocket 连接关闭代码,并将reason
属性初始化为将 UTF-8 解码无 BOM 应用于 WebSocket 连接关闭原因 的结果。 [WSP]
用户代理不得以任何方式向脚本传达失败信息,这将使脚本能够区分以下情况:
-
无法解析其主机名的服务器。
-
无法成功路由数据包的服务器。
-
在指定端口拒绝连接的服务器。
-
未能正确执行 TLS 握手的服务器(例如,无法验证服务器证书)。
-
未完成打开握手的服务器(例如,因为它不是 WebSocket 服务器)。
-
发送了正确打开握手的 WebSocket 服务器,但指定的选项导致客户端断开连接(例如,服务器指定了客户端未提供的子协议)。
-
在成功完成打开握手后突然关闭连接的 WebSocket 服务器。
在所有这些情况下,WebSocket 连接关闭代码将为1006,正如WebSocket Protocol所要求的。[WSP]
允许脚本区分这些情况将使脚本能够在攻击前探测用户的本地网络。
特别是,这意味着代码1015不会被用户代理使用(除非服务器在其关闭帧中错误地使用了它,当然)。
在本节中排队的所有任务的任务源是WebSocket 任务源。
5. Ping 和 Pong 帧
WebSocket 协议定义了可用于保持连接、心跳检测、网络状态探测、延迟测量等用途的 Ping 和 Pong 帧。这些帧目前未在 API 中暴露。
用户代理可以根据需要发送 ping 和未请求的 pong 帧,例如为了保持本地网络 NAT 映射、检测失败的连接或向用户显示延迟指标。用户代理不得使用 ping 或未请求的 pong 来帮助服务器;假定服务器会在适当的时候请求 pong,以满足服务器的需求。
6. CloseEvent 接口
WebSocket
对象使用CloseEvent
接口处理close
事件:
[Exposed =(Window ,Worker )]interface :
CloseEvent Event {(
constructor DOMString ,
type optional CloseEventInit = {});
eventInitDict readonly attribute boolean wasClean ;readonly attribute unsigned short code ;readonly attribute USVString reason ; };dictionary :
CloseEventInit EventInit {boolean =
wasClean false ;unsigned short = 0;
code USVString = ""; };
reason
- event .
wasClean
-
如果连接已清理关闭,则返回 true;否则返回 false。
- event .
code
-
返回服务器提供的 WebSocket 连接关闭代码。
- event .
reason
-
返回服务器提供的 WebSocket 连接关闭原因。
wasClean
属性必须返回初始化时的值。它表示连接是否已清理关闭。
code
属性必须返回初始化时的值。它表示服务器提供的 WebSocket 连接关闭代码。
reason
属性必须返回初始化时的值。它表示服务器提供的 WebSocket 连接关闭原因。
7. 垃圾回收
WebSocket
对象,其 就绪状态
被设置为 CONNECTING
(0) 时,
当 事件循环
上次达到 步骤1 时,
如果注册了任何 open
事件、message
事件、error
事件或 close
事件的事件监听器,则不能进行垃圾回收。
WebSocket
对象,其 就绪状态
被设置为 OPEN
(1) 时,
当 事件循环
上次达到 步骤1 时,
如果注册了任何 message
事件、error
事件或 close
事件的事件监听器,则不能进行垃圾回收。
WebSocket
对象,其 就绪状态
被设置为 CLOSING
(2) 时,
当 事件循环
上次达到 步骤1 时,
如果注册了任何 error
事件或 close
事件的事件监听器,则不能进行垃圾回收。
如果一个 WebSocket
对象与 已建立的连接
有待传输到网络的数据,则不能进行垃圾回收。[WSP]
如果一个 WebSocket
对象在其连接仍然打开时被垃圾回收,用户代理必须 启动
WebSocket 关闭握手,并且不为关闭消息提供状态代码。[WSP]
如果用户代理要 消失 一个 WebSocket
对象(当 文档
对象消失时会发生这种情况),用户代理必须遵循以下列表中的第一组适当步骤:
致谢
在 2021 年创建此标准之前,此处的文本在 HTML Standard 和 Fetch Standard 中维护。感谢所有为开发规范做出贡献的贡献者,特别是作为原作者的 Ian Hickson 和 Anne van Kesteren。
感谢 devsnek 和 平野裕 (Yutaka Hirano) 在 WebSockets Standard 创建后所做的贡献。
此标准由 Adam Rice (Google, ricea@chromium.org) 编写。
知识产权
版权所有 © WHATWG (Apple, Google, Mozilla, Microsoft)。本作品采用 知识共享署名 4.0 国际许可协议 进行许可。至于它的某些部分被纳入源代码中,这些部分在源代码中按照 BSD 3-Clause License 进行许可。
这是现行标准。那些对专利审查版感兴趣的人可以查看 现行标准审查草案。