این کار باعث حذف صفحه ی "4. 开始握手"
می شود. لطفا مطمئن باشید.
4.1. 客户端要求
为了_建立WebSocket连接_,客户端打开一个连接并发送本节定义的握手。连接最初被定义为处于连接中(CONNECTING)状态。客户端需要提供主机(/host/)、端口(/port/)、资源名称(/resource name/)和安全标志(/secure/ flag),这些是第3节讨论的WebSocket URI的组成部分,以及要使用的协议(/protocols/)和扩展(/extensions/)列表。此外,如果客户端是Web浏览器,它还提供来源(/origin/)。
在受控环境中运行的客户端,例如,与特定运营商绑定的移动手持设备上的浏览器,可以将连接的管理卸载给网络上的另一个代理。在这种情况下,根据本规范的目的,客户端被认为包括手持设备软件和任何此类代理。
当客户端要根据一组(主机(/host/)、端口(/port/)、资源名称(/resource name/)和安全标志(/secure/ flag))以及要使用的协议(/protocols/)和扩展(/extensions/)列表,在Web浏览器的情况下还有来源(/origin/),来_建立WebSocket连接_时,它必须打开一个连接,发送一个开始握手,并读取服务器的握手响应。如何打开连接、开始握手中应发送什么以及如何解释服务器的响应的确切要求如下。在以下文本中,我们将使用第3节中的术语,例如“/host/”和“/secure/ flag”,如该节所定义。
传入此算法的WebSocket URI的组成部分(主机(/host/)、端口(/port/)、资源名称(/resource name/)和安全标志(/secure/ flag))必须根据第3节中指定的WebSocket URI的规范有效。如果任何组成部分无效,客户端必须_失败WebSocket连接_并中止这些步骤。
如果客户端已经与由主机(/host/)和端口(/port/)对标识的远程主机(IP地址)建立了WebSocket连接,即使远程主机以另一个名称被知道,客户端必须等待该连接建立或该连接失败。在连接中状态(CONNECTING)下不能有多于一个连接。如果同时尝试与同一IP地址的多个连接,客户端必须序列化它们,以便一次只有一个连接通过以下步骤。
如果客户端无法确定远程主机的IP地址(例如,因为所有通信都是通过自行执行DNS查询的代理服务器进行的),那么客户端必须假设为了这一步的目的,每个主机名都指向一个不同的远程主机,而客户端应该将同时挂起的连接总数限制在一个合理的低数量(例如,客户端可能允许同时挂起到a.example.com和b.example.com的连接,但如果请求到单个主机的三十个同时连接,可能不被允许)。例如,在Web浏览器上下文中,客户端需要考虑用户打开的标签页数量来设置同时挂起连接数量的限制。
注意:这使得脚本通过仅打开大量WebSocket连接到远程主机来执行拒绝服务攻击变得更加困难。服务器可以通过在关闭连接之前暂停来进一步减轻自身在受到攻击时的负载,因为这将降低客户端重新连接的速率。
注意:客户端与单个远程主机建立的WebSocket连接数量没有限制。服务器可以拒绝接受来自具有过多现有连接的主机/IP地址的连接,或者在遭受高负载时断开占用资源过多的连接。
示例:例如,如果客户端对所有流量使用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连接单独从其他代理中选择显式UI的实现使用SOCKS5 [RFC1928]代理进行WebSocket连接(如果可用),或者如果不可用,优先选择为HTTPS连接配置的代理而不是为HTTP连接配置的代理。
对于代理自动配置脚本的目的,必须使用从主机(/host/)、端口(/port/)、资源名称(/resource name/)和安全标志(/secure/ flag)构造的URI,使用第3节中给出的WebSocket URI的定义。
注意:WebSocket协议可以在代理自动配置脚本中从方案中识别(对于未加密连接为"ws",对于加密连接为"wss")。
如果无法打开连接,无论是因为直接连接失败还是因为使用的任何代理返回错误,客户端必须_失败WebSocket连接_并中止连接尝试。
如果/secure/为true,客户端必须在打开连接后和发送握手数据之前通过连接执行TLS握手[RFC2818]。如果失败(例如,无法验证服务器的证书),则客户端必须_失败WebSocket连接_并中止连接。否则,此通道上的所有进一步通信必须通过加密隧道进行[RFC5246]。
客户端必须在TLS握手中使用服务器名称指示扩展[RFC6066]。
一旦与服务器建立了连接(包括通过代理或通过TLS加密隧道的连接),客户端必须向服务器发送开始握手。握手包括一个HTTP升级请求,以及一系列必需和可选的头字段。此握手的要求如下。
握手必须是[RFC2616]指定的有效HTTP请求。
请求的方法必须是GET,HTTP版本必须至少是1.1。
例如,如果WebSocket URI是"ws://example.com/chat",发送的第一行应该是"GET /chat HTTP/1.1"。
请求的"Request-URI"部分必须与第3节中定义的资源名称(相对URI)匹配,或者是一个绝对http/https URI,当解析时,具有与相应的ws/wss URI匹配的资源名称、主机和端口。
请求必须包含一个|Host|头字段,其值包含主机(/host/)加上可选的":"后跟端口(/port/)(当不使用默认端口时)。
请求必须包含一个|Upgrade|头字段,其值必须包含“websocket”关键字。
请求必须包含一个|Connection|头字段,其值必须包含“Upgrade”令牌。
请求必须包含一个名为|Sec-WebSocket-Key|的头字段。该头字段的值必须是一个随机选择的16字节值的nonce,该值已经被base64编码(参见[RFC4648]的第4节)。对于每个连接,nonce必须随机选择。
注意:例如,如果随机选择的值是字节序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,头字段的值将是"AQIDBAUGBwgJCgsMDQ4PEC=="
例如,如果从www.example.com下载的代码尝试建立与ww2.example.com的连接,头字段的值将是"http://www.example.com"。
注意:尽管本文档的草案版本(-09、-10、-11和-12)已发布(它们主要由编辑性更改和澄清组成,而不是对线路协议的更改),但值9、10、11和12未被用作Sec-WebSocket-Version的有效值。这些值已在IANA注册表中保留,但未被且将不被使用。
请求可以包含一个名为|Sec-WebSocket-Protocol|的头字段。如果存在,此值表示客户端希望使用的一个或多个逗号分隔的子协议,按优先顺序排列。构成此值的元素必须是非空字符串,字符范围为U+0021到U+007E,不包括[RFC2616]中定义的分隔符字符,并且必须都是唯一的字符串。此头字段值的ABNF为1#token,其中构造和规则的定义如[RFC2616]中所给。
请求可以包含一个名为|Sec-WebSocket-Extensions|的头字段。如果存在,此值表示客户端希望使用的协议级扩展。此头字段的解释和格式在第9.1节中描述。
请求可以包含任何其他头字段,例如,cookie [RFC6265]和/或与认证相关的头字段,如|Authorization|头字段[RFC2616],这些头字段根据定义它们的文档进行处理。
一旦客户端的开始握手已发送,客户端必须等待服务器的响应,然后再发送任何进一步的数据。客户端必须按如下方式验证服务器的响应:
如果从服务器收到的状态码不是101,客户端按照HTTP [RFC2616]程序处理响应。特别是,如果收到401状态码,客户端可能执行认证;服务器可能使用3xx状态码重定向客户端(但客户端不要求遵循它们),等等。否则,按如下方式进行。
如果响应缺少|Upgrade|头字段或|Upgrade|头字段包含的值不是“websocket”的ASCII不区分大小写匹配,客户端必须_失败WebSocket连接_。
如果响应缺少|Connection|头字段或|Connection|头字段不包含与值“Upgrade”的ASCII不区分大小写匹配的令牌,客户端必须_失败WebSocket连接_。
如果响应缺少|Sec-WebSocket-Accept|头字段或|Sec-WebSocket-Accept|包含的值不是|Sec-WebSocket-Key|(作为字符串,不是base64解码)与字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"的连接的base64编码的SHA-1,但忽略任何前导和尾随空白,客户端必须_失败WebSocket连接_。
如果响应包含|Sec-WebSocket-Extensions|头字段,并且此头字段指示使用客户端握手中未出现的扩展(服务器指示了客户端未请求的扩展),客户端必须_失败WebSocket连接_。(解析此头字段以确定请求哪些扩展在第9.1节中讨论。)
如果响应包含|Sec-WebSocket-Protocol|头字段,并且此头字段指示使用客户端握手中未出现的子协议(服务器指示了客户端未请求的子协议),客户端必须_失败WebSocket连接_。
如果服务器的响应不符合本节和第4.2.2节中定义的服务器握手的要求,客户端必须_失败WebSocket连接_。
请注意,根据[RFC2616],HTTP请求和HTTP响应中的所有头字段名称都不区分大小写。
如果服务器的响应按上述提供的方式验证,就认为_WebSocket连接已建立_,并且WebSocket连接处于OPEN状态。_正在使用的扩展_被定义为(可能为空的)字符串,其值等于服务器握手中提供的|Sec-WebSocket-Extensions|头字段的值,或者如果该头字段在服务器握手中未出现,则为null值。_正在使用的子协议_被定义为服务器握手中|Sec-WebSocket-Protocol|头字段的值,或者如果该头字段在服务器握手中未出现,则为null值。此外,如果服务器握手中的任何头字段指示应设置cookie(如[RFC6265]所定义),这些cookie被称为_服务器开始握手期间设置的cookie_。
4.2. 服务器端要求
服务器可以将连接的管理卸载给网络上的其他代理,例如负载均衡器和反向代理。在这种情况下,根据本规范的目的,服务器被认为包括服务器端基础设施的所有部分,从终止TCP连接的第一个设备一直到处理请求和发送响应的服务器。
示例:一个数据中心可能有一个服务器,它以适当的握手响应WebSocket请求,然后将连接传递给另一个服务器来实际处理数据帧。根据本规范的目的,“服务器”是这两台计算机的组合。
4.2.1. 读取客户端的开启握手
当客户端开始WebSocket连接时,它会发送其握手的一部分。服务器必须至少解析握手的一部分,以获取生成服务器握手部分所需的信息。
客户端的开启握手包括以下部分。如果服务器在读取握手时发现客户端发送的握手与下面的描述不匹配(注意根据[RFC2616],头字段的顺序并不重要),包括但不限于对握手组件的ABNF语法的任何违反,服务器必须停止处理客户端的握手,并返回一个带有适当错误代码的HTTP响应(例如400 Bad Request)。
一个HTTP/1.1或更高版本的GET请求,包括一个“Request-URI”[RFC2616],应该被解释为第3节定义的/资源名称/(或包含/资源名称/的绝对HTTP/HTTPS URI)。
一个包含服务器权限的|Host|头字段。
一个包含值“websocket”的|Upgrade|头字段,视为ASCII不区分大小写的值。
一个包含令牌“Upgrade”的|Connection|头字段,视为ASCII不区分大小写的值。
一个|Sec-WebSocket-Key|头字段,其值为base64编码(参见[RFC4648]的第4节),解码后长度为16字节。
一个|Sec-WebSocket-Version|头字段,其值为13。
可选地,一个|Origin|头字段。所有浏览器客户端都会发送此头字段。缺少此头字段的连接尝试不应被解释为来自浏览器客户端。
可选地,一个|Sec-WebSocket-Protocol|头字段,带有一个值列表,指示客户端希望使用哪些协议,按优先顺序排列。
可选地,一个|Sec-WebSocket-Extensions|头字段,带有一个值列表,指示客户端希望使用哪些扩展。此头字段的解释在第9.1节中讨论。
可选地,其他头字段,例如用于发送cookie或向服务器请求认证的头字段。根据[RFC2616],未知头字段将被忽略。 4.2.2. 发送服务器的开启握手
当客户端与服务器建立WebSocket连接时,服务器必须完成以下步骤来接受连接并发送服务器的开启握手。
如果连接发生在HTTPS(HTTP-over-TLS)端口上,通过连接执行TLS握手。如果失败(例如,客户端在扩展客户端hello "server_name"扩展中指示了服务器不托管的主机名),则关闭连接;否则,连接的所有后续通信(包括服务器的握手)必须通过加密隧道进行[RFC5246]。
服务器可以执行额外的客户端认证,例如,通过返回带有相应|WWW-Authenticate|头字段的401状态码,如[RFC2616]中所述。
服务器可以使用3xx状态码[RFC2616]重定向客户端。请注意,这一步可以与上述可选的认证步骤一起发生,之前或之后。
确定以下信息:
/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|头字段,指示服务器能够理解的版本。
/resource name/:服务器提供的服务的标识符。如果服务器提供多个服务,则该值应该从客户端握手中的资源名称中派生,即GET方法的"Request-URI" [RFC2616]。如果请求的服务不可用,服务器必须发送适当的HTTP错误代码(例如404 Not Found)并中止WebSocket握手。
/subprotocol/:表示服务器准备使用的子协议的单个值,或null。所选值必须从客户端握手中派生,特别是通过选择服务器愿意用于此连接的|Sec-WebSocket-Protocol|字段中的值之一(如果有)。如果客户端的握手未包含此类头字段,或者如果服务器不同意客户端请求的任何子协议,则唯一可接受的值是null。此字段的缺失等同于null值(意味着如果服务器不希望同意建议的子协议之一,它必须不在其响应中发送回|Sec-WebSocket-Protocol|头字段)。对于这些目的,空字符串不同于null值,且不是此字段的合法值。此头字段值的ABNF为(token),其中构造和规则的定义如[RFC2616]中所给。
/extensions/:表示服务器准备使用的协议级扩展的(可能为空的)列表。如果服务器支持多个扩展,则该值必须从客户端握手中派生,特别是通过选择|Sec-WebSocket-Extensions|字段中的一个或多个值。此字段的缺失等同于null值。对于这些目的,空字符串不同于null值。客户端未列出的扩展不得列出。如何选择和解释这些值的方法在第9.1节中讨论。
如果服务器选择接受传入连接,它必须以有效的HTTP响应回复,指示以下内容。
一个带有101响应代码的Status-Line,如RFC 2616 [RFC2616]所述。这样的响应可能看起来像"HTTP/1.1 101 Switching Protocols"。
一个值为"websocket"的|Upgrade|头字段,如RFC 2616 [RFC2616]所述。
一个值为"Upgrade"的|Connection|头字段。
一个|Sec-WebSocket-Accept|头字段。此头字段的值是通过将上述第4步中定义的/key/与字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"连接,取此连接值的SHA-1哈希以获得20字节的值,并对此20字节的哈希进行base64编码(参见[RFC4648]的第4节)构建的。
可选地,一个|Sec-WebSocket-Protocol|头字段,其值为第4节第4步中定义的/subprotocol/。
可选地,一个|Sec-WebSocket-Extensions|头字段,其值为第4节第4步中定义的/extensions/。如果要使用多个扩展,它们可以全部列在单个|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语法变体时,解码后的值必须符合'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|头字段响应,包含它愿意使用的所有版本。此时,如果客户端支持其中一个广告版本,它可以使用新的版本值重复WebSocket握手。
以下示例演示了上述的版本协商:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 25
服务器的响应可能如下所示:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13, 8, 7
请注意,服务器的最后响应也可能如下所示:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13
Sec-WebSocket-Version: 8, 7
客户端现在重复符合版本13的握手:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 13
این کار باعث حذف صفحه ی "4. 开始握手"
می شود. لطفا مطمئن باشید.