要建立WebSocket連接,客戶端打開一個連接并發(fā)送一個握手,就像本節(jié)中定義那樣。一個連接最初被定義為一個CONNECTING狀態(tài)??蛻舳藢⑿枰峁┮粋€/host/、/port/、/resource name/、和/secure/標(biāo)記,它們都是在第三章討論的WebSocket URI的組件,連同一起使用的一個/protocols/和/extensions/列表。此外,如果客戶端是一個web瀏覽器,它提供/origin/??蛻舳诉\行在一個受控環(huán)境,例如綁定到特定運營商的手機上的瀏覽器,可以下移(offload)連接管理到網(wǎng)絡(luò)上的另一個代理。在這種情況下,用于本規(guī)范目的的客戶端被認(rèn)為包括手機軟件和任何這樣的代理。
當(dāng)客戶端要建立一個WebSocket連接,給定一組(/host/、/port/、/resource name/、和/secure/標(biāo)記)、連同一起使用的一個/protocols/和/extensions/列表、和在web瀏覽器情況下的一個/origin/,它必須打開一個連接、發(fā)送一個打開階段握手、并讀取服務(wù)器響應(yīng)中的握手。應(yīng)如何打開連接的確切要求、在打開階段握手應(yīng)發(fā)送什么、以及應(yīng)如何解釋服務(wù)器響應(yīng),在本節(jié)如下所述。在下面的文本中,我們將使用第三章的術(shù)語,如定義在那章的“/host” 、和“/sucure/標(biāo)記” 。
傳入該算法的WebSocket URI組件(/host/、/port/、/resource name/、和/secure/ 標(biāo)記) 根據(jù)指定在第3章的WebSocket URI規(guī)范,必須是有效的。如果任何組件是無效的,客戶端必須失敗WebSocket連接并終止這些步驟。
如果客戶端已經(jīng)有一個到通過主機/host/和端口/port/對標(biāo)識的遠程主機(IP地址)的WebSocket連接,即使遠程主機是已知的另一個名字,客戶端必須等待直到連接已建立或由于連接已失敗。必須不超過一個連接處于CONNECTING 狀態(tài)。如果同時多個連接到同一個IP地址,客戶端必須 序列化它們,以致一次不多于一個連接在以下步驟中運行。
如果客戶端不能決定遠程主機的IP地址(例如,因為所有通信是通過代理服務(wù)器本身進行DNS查詢),那么客戶端必須假定這步的目的是每一個主機名引用一個不同遠程主機,且相反,客戶端應(yīng)該限制同時掛起的連接總數(shù)為一個適當(dāng)?shù)偷臄?shù)(例如,客戶端可能允許到a.example.com和b.example.com同時掛起連接,但如果30個同時連接到同一個請求的主機,那可能是不允許的)。例如,在一個web瀏覽器上下文中,客戶端需要考慮用戶已經(jīng)打開的標(biāo)簽數(shù)量,在設(shè)置同時掛起的連接數(shù)量的限制時。
注意:這使得它很難僅通過打開大量的WebSocket連接到遠程主機為腳本執(zhí)行一個拒絕服務(wù)攻擊。當(dāng)攻擊在關(guān)閉連接之前被暫停時,服務(wù)器可以進一步降低自身的負(fù)載,因為這將降低客戶端重新連接的速度。
注意:沒有限制一個客戶端可以與單個遠程主機有的已建立的WebSocket連接數(shù)量。服務(wù)器可以拒絕接受來自具有大量的現(xiàn)有連接的主機/IP地址的連接或當(dāng)遭受高負(fù)載時斷開占用資源的連接。
使用代理:當(dāng)有WebSocket協(xié)議連接主機/host/和端口/port/時,如果客戶端配置使用代理,那么客戶端應(yīng)該連接到代理并要求它打開一個到由/host/給定主機和/port/給定端口的TCP連接。 例子:例如,如果客戶端為所有信息傳輸使用一個HTTP代理,那么如果它試圖連接到服務(wù)器example.com的80端口,它可能會發(fā)送以下行到代理服務(wù)器:
CONNECT example.com:80 HTTP/1.1
Host: example.com
如果還有密碼,連接可能看起來像:
CONNECT example.com:80 HTTP/1.1
Host: example.com
Proxy-authorization: Basic ZWRuYW1vZGU6bm9jYXBlcyE=
如果客戶端沒有配置使用一個代理,那么應(yīng)該打開一個直接TCP連接到由/host/給定的主機和/port/給定的端口。
注意:不暴露明確的UI來為WebSocket連接選擇一個獨立于其他代理的代理實現(xiàn),鼓勵使用SOCKS5[RFC1928]代理用于WebSocket連接,如果有的話,或做不到這一點,選擇為HTTPS連接配置代理勝過為HTTP連接配置代理。
為了代理自動配置腳本,傳給函數(shù)的URI必須從/host/、/port/、/resource name/、和/secure/標(biāo)記來構(gòu)造,使用第三章給定的WebSocket URI定義。
注意:WebSocket協(xié)議可以在代理自動配置腳本中從模式中識別(“ws”用于未加密的連接和“wss”用于加密的連接)。
如果連接無法打開,或者因為直接連接失敗或者因為任何使用的代理返回一個錯誤,那么客戶端必須失敗WebSocket連接并終止連接嘗試。
客戶端必須在TLS握手中使用服務(wù)器命名指示(Server Name Indication)擴展[RFC6066]。
一旦一個到服務(wù)器的連接連接(包括通過代理或在TLS加密隧道之上的連接),客戶端必須發(fā)送一個打開階段握手到服務(wù)器。該握手包括一個HTTP Upgrade請求,連同一個必需的和可選的頭字段列表。該握手的要求如下所示。
握手必須是像[RFC2616]指定的那樣的有效的HTTP請求。
請求方法必須是GET、且HTTP版本必須是至少1.1。
例如,如果WebSocket URI是“ws://example.com/chat”,發(fā)送的第一行應(yīng)該是“GET /chat HTTP/1.1”。
請求的“Request-URI”部分必須匹配定義在第三章的(一個相對URI)/resource name/或是一個絕對的http/https URI,當(dāng)解析時,有一個/resource name/、/host/、和/port/匹配相應(yīng)的ws/wss URI。
請求必須包含一個|Host|頭字段,其值包含/host/加上可選的“:”后跟/port/(當(dāng)沒用默認(rèn)端口時)。
請求必須包含一個|Upgrade|頭字段,其值必須包含“websocket”關(guān)鍵字。
請求必須包含一個|Connection|頭字段,其值必須包含“Upgrade”標(biāo)記。
請求必須包含一個名字為| Sec-WebSocket-Key |的頭字段,這個頭字段的值必須是臨時(nonce)組成的一個隨機選擇的已經(jīng)base64編碼的(參見[RFC4648]第4章)16位的值。臨時必須是為每個連接隨機選擇的。
注意:作為一個例子,如果隨機選擇的值是字節(jié)序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,頭字段的值必須是“AQIDBAUGBwgJCgsMDQ4PEC==”
如果請求來自一個瀏覽器客戶端,請求必須包含一個名字為|Origin|[RFC6454]的頭字段。如果連接是來自非瀏覽器客戶端,如果該客戶端的語義匹配描述在這的用于瀏覽器的使用情況時,請求可以包含這個字段。該頭字段的值是在建立正運行的連接代碼中的環(huán)境的origin 的ASCII序列化。參考[RFC6454]獲取如果構(gòu)造該頭字段的值的詳細信息。
作為一個例子,如果從www.example.com下載的代碼試圖建立到ww2.example.com的連接,該頭字段的值將是“http://www.example.com”。
請求必須包含一個名字為|Sec-WebSocket-Version|的頭字段。該頭字段的值必須是13。
注意:盡管本文檔的草案版本(-09、-10、-11、和-12)發(fā)布了(它們多不是編輯上的修改和澄清而不是改變電報協(xié)議[wire protocol]),值9、10、11、和12不被用作有效的Sec-WebSocket-Version。這些值被保留在IANA注冊中心,但并將不會被使用。
請求可以包含一個名字為|Sec-WebSocket-Protocol|的頭字段。如果存在,該值表示一個或多個逗號分割的客戶端想要表達的子協(xié)議,按優(yōu)先順序排列。包含該值的元素必須是非空字符串,且字符在U+0021到U+007E范圍內(nèi)但不包含定義在[RFC2616]中的分割字符且必須所有是唯一的字符串。用于該頭字段值的ABNF是1#token,其構(gòu)造和規(guī)則定義在[RFC2616]給出。
請求可以包含一個名字為|Sec-WebSocket-Extensions|的頭字段。如果存在,該值表示客戶端想要表達的協(xié)議級的擴展。該頭字段的解釋和格式描述在第9.1節(jié)。
一旦客戶端的打開階段握手已經(jīng)發(fā)送,客戶端在發(fā)送任何進一步數(shù)據(jù)之前必須等待自服務(wù)器的一個響應(yīng)。客戶端必須驗證服務(wù)器的響應(yīng),如下所示:
如果收到的服務(wù)器的狀態(tài)碼不是101,客戶端處理每個HTTP[RFC2616]程序的響應(yīng)。尤其是,如果收到一個401狀態(tài)碼客戶端可能執(zhí)行身份驗證;服務(wù)器可能使用一個3xx狀態(tài)碼重定向客戶端(但客戶端不需要跟隨他們),等等。否則,按以下步驟處理。
如果響應(yīng)缺少一個|Upgrade|頭字段或|Upgrade|頭字段包含的值不是一個不區(qū)分大小寫的ASCII匹配值“websocket”,客戶端必須失敗WebSocket連接。
如果想要缺少一個|Connection|頭字段或|Connection|頭字段不包含一個不區(qū)分大小的ASCII匹配值“Upgrade”符號,客戶端必須失敗WebSocket連接。
如果想要缺少一個|Sec-WebSocket-Accept|頭字段或|Sec-WebSocket-Accept|包含一個不是|Sec-WebSocket-Key|(一個字符串,不是base64編碼的)與字符串“258EAFA5- E914-47DA-95CA-C5AB0DC85B11”但忽略任何前導(dǎo)和結(jié)尾空格相關(guān)聯(lián)的base64編碼的SHA-1值,客戶端必須失敗WebSocket連接。
如果響應(yīng)包含一個|Sec-WebSocket-Extensions|頭字段且此頭字段表示使用一個擴展但沒有出現(xiàn)在客戶端握手中(服務(wù)器表示的一個擴展,不是客戶端請求的),客戶端必須失敗WebSocket連接。解析該頭字段以確定請求了哪些擴展在9.1節(jié)討論。
如果服務(wù)器響應(yīng)不符合定義在本節(jié)和4.2.2節(jié)中的服務(wù)器握手的要求,客戶端必須失敗WebSocket連接。
請注意,根據(jù)[RFC2616],所有命名在HTTP請求和HTTP響應(yīng)中的頭字段是不區(qū)分大小寫的。
如果服務(wù)器響應(yīng)驗證了以上提供的,這是說,WebSocket連接建立了且WebSocket連接處于OPEN狀態(tài)。使用中的擴展被定義為一個(可能空的)字符串,其值等于服務(wù)器握手中提供的|Sec-WebSocket-Extensions|頭字段的值或如果在服務(wù)器握手中沒有該頭字段則為null值。使用中的子協(xié)議被定義為服務(wù)器握手中的|Sec-WebSocket-Protocol|頭字段的值或如果在服務(wù)器握手中沒有該頭字段則為null值。另外,如果在服務(wù)器握手中表示cookie應(yīng)該被設(shè)置(定義在[RFC6265])的任何頭字段,這些cookie被稱為在服務(wù)器打開階段握手期間的Cookie設(shè)置。
服務(wù)器可以下移(offload)連接管理到網(wǎng)絡(luò)上的其他代理,例如,負(fù)載均衡和反向代理。在這樣的情況下,用于本規(guī)范的目的的服務(wù)器被認(rèn)為是包括服務(wù)器端基礎(chǔ)設(shè)施的所有部分,從開始的設(shè)備到終止TCP連接,處理請求和發(fā)送響應(yīng)的服務(wù)器的所有方式。
例如:一個數(shù)據(jù)中心可能有一個用適當(dāng)?shù)奈帐謥眄憫?yīng)WebSocket請求的服務(wù)器,并接著傳遞連接到另一個服務(wù)器來真正處理數(shù)據(jù)幀。對于本規(guī)范的目的,“服務(wù)器”是結(jié)合了兩種計算機。
當(dāng)客戶端開始一個WebSocket連接,它發(fā)送它的打開階段握手部分。服務(wù)器必須至少解析這個握手為了獲取必要的信息來生成服務(wù)器握手部分。
客戶端打開階段握手包括以下部分。如果服務(wù)器,當(dāng)讀取握手時,發(fā)現(xiàn)客戶端沒有發(fā)送一個匹配下面描述的握手(注意,按照[RFC2616],頭字段順序是不重要的),包括但不限制任何違反ABNF語法指定的握手組件,服務(wù)器必須停止處理客戶端握手并返回一個具有一個適當(dāng)錯誤碼的(例如400錯誤的請求)HTTP響應(yīng)。
一個HTTP/1.1或更高版本的GET請求,包括一個“Request-URI” [RFC2616]應(yīng)該被解釋為定義在第3章的/resource name/(或一個包含/resource name/的絕對HTTP/HTTPS URI)。
一個|Host|頭字段包含服務(wù)器的權(quán)限。
一個|Upgrade|頭字段包含值“websocket”,視為一個不區(qū)分大小寫的ASCII值。
一個|Connection|頭字段包含符號“Upgrade”,視為一個不區(qū)分大小寫的ASCII值。
一個|Sec-WebSocket-Key|頭字段,帶有一個base64編碼的值(參見[RFC4648]第4章),當(dāng)解碼時,長度是16字節(jié)。
一個|Sec-WebSocket-Version|頭字段,帶有值13。
可選的,一個|Origin|頭字段。該頭字段由所有瀏覽器客戶端發(fā)送。一個試圖缺失此頭字段的連接不應(yīng)該被解釋為來自瀏覽器客戶端。
可選的,一個|Sec-WebSocket-Protocol|頭字段,帶有表示客戶端想要表達的協(xié)議的值列表,按優(yōu)先順序排列。
可選的,一個|Sec-WebSocket-Extensions|頭字段,帶有表示客戶端想要表達的擴展的值列表。此頭字段的解釋在9.1節(jié)討論。
當(dāng)客戶端建議一個到服務(wù)器的WebSocket連接,服務(wù)器必須完成以下步驟來接受該連接并發(fā)送服務(wù)器的打開階段握手。
如果連接發(fā)送在一個HTTPS (HTTP-over-TLS)端口上,在連接之上執(zhí)行一個TLS握手。如果失敗了(例如,在擴展的客戶端hello“server_name”擴展中的客戶端指示的一個主機名,服務(wù)器對主機不可用),則關(guān)閉該連接;否則,用于該連接的所有進一步的通信必須貫穿加密的隧道[RFC5246]。
服務(wù)器可以執(zhí)行額外的客戶端身份認(rèn)證,例如,返回401狀態(tài)碼與描述在[RFC2616]中的相關(guān)的|WWW-Authenticate|頭字段。
服務(wù)器可以使用3xx狀態(tài)碼[RFC2616]重定向客戶端。注意,此步驟可能連同,之前,或之后的可選的上面描述的身份驗證步驟一起發(fā)生。
/origin/
客戶端握手中的|Origin|頭字段表示建立連接的腳本的來源。Origin是序列化為ASCII并轉(zhuǎn)換為小寫。服務(wù)器可以使用這個信息作為決定是否接受傳入連接的一部分。如果服務(wù)器沒有驗證origin,它將接受來自任何地方的連接。如果服務(wù)器不想接受這個連接,它必須返回一個適當(dāng)?shù)腍TTP錯誤碼(例如,403 Forbidden)并中斷描述在本章中的WebSocket握手。更多詳細信息,請參閱第10章。
/key/
在客戶端握手中的|Sec-WebSocket-Key|頭字段包括一個base64編碼的值,如果解碼,長度是16字節(jié)。這個(編碼的)值用在創(chuàng)建服務(wù)器握手時來表示接受連接。服務(wù)器沒必要使用base64解碼|Sec-WebSocket-Key|值。
/version/
客戶端握手中的|Sec-WebSocket-Version|頭字段包括客戶端試圖通信的WebSocket協(xié)議的版本。如果該版本沒有匹配服務(wù)器理解的一個版本,服務(wù)器必須中斷描述在本節(jié)的WebSocket握手并替代返回一個適當(dāng)?shù)腍TTP錯誤碼(例如,426 Upgrade Required)且一個|Sec-WebSocket-Version|頭字段表示服務(wù)器能理解的版本。
/resource name/
由服務(wù)器提供的服務(wù)的標(biāo)識符。如果服務(wù)器提供多個服務(wù),那么該值應(yīng)該源自客戶端我手中的GET方法的“Request-URI”中給定的資源名。如果請求的服務(wù)器不可用,服務(wù)器必須發(fā)生一個適當(dāng)?shù)腍TTP錯誤碼(例如404 NotFound)并中斷WebSocket握手。
/subprotocol/
或者一個代表服務(wù)器準(zhǔn)備使用的子協(xié)議的單個值或者null。選擇的值必須源自客戶端握手,從|Sec-WebSocket-Protocol|字段具體地選擇一個值,服務(wù)器將使用它用于這個連接(如果有)。如果客戶端握手不包含這樣一個頭字段或如果服務(wù)器不同意任何客戶端請求的子協(xié)議,僅接受的值為null。這個字段不存在等價于null值(意思是如果服務(wù)器不想同意任何建議的子協(xié)議,它必須在它的響應(yīng)中不發(fā)送回一個|Sec-WebSocket-Protocol|頭字段)。用于這些目的,空字符串與null值是不一樣的,且它不是這個字段合法的值。用于該頭字段的值A(chǔ)BNF是(符號),構(gòu)造定義和規(guī)則在[RFC2616]中給出。
/extensions/
表示服務(wù)器準(zhǔn)備使用的協(xié)議級別擴展的一個列表(可能為空)。如果服務(wù)器支持多個擴展,那么該值必須源自客戶端握手,通過從|Sec-WebSocket-Extensions|字段具體地選擇一個或多個值。這個字段不存在等價于null值。用于這些目的,空字符串與null值是不一樣的??蛻粑戳谐龅臄U展必須不被列出。那些值應(yīng)該被選擇和解釋的方法在9.1節(jié)討論。
如果服務(wù)器選擇接受傳入的連接,它必須以一個有效的表示以下的HTTP響應(yīng)應(yīng)答。
一個按照RFC2616[RFC2616]帶有101響應(yīng)碼的Status-Line。這樣的響應(yīng)可能看起來像“HTTP/1.1 101 Switching Protocols”。
一個按照RFC2616[RFC2616]帶有值“websocket”的|Upgrade|頭字段。
一個帶有“Upgrade”的| Connection |頭字段。
該頭字段的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==”,服務(wù)器將追加字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”為字符串dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11形式。
服務(wù)器將采取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|頭字段中被返回。
可選的,一個|Sec-WebSocket-Protocol|頭字段,帶有一個定義在4.2.2節(jié)第4步的值/subprotocol/。
可選的,一個|Sec-WebSocket-Extensions|頭字段,帶有一個定義在4.2.2節(jié)第4步的值/ extensions /。如果使用多個擴展,那么可以把所有都列在一個|Sec-WebSocket-Extensions|頭字段中或者分配到|Sec-WebSocket-Extensions|頭字段的多個實例之間。 這就完成了服務(wù)器握手。如果服務(wù)器完整這些步驟且沒有中斷WebSocket握手,服務(wù)器認(rèn)為WebSocket連接已建立且WebSocket連接處于OPEN狀態(tài)。此時,服務(wù)器可以開始發(fā)送(和接收)數(shù)據(jù)了。
本節(jié)使用的ABNF語法/規(guī)范來自[RFC2616]第2.1節(jié),包括“隱式*LWS規(guī)則”。
注意,以下ABNF約定用于本節(jié)中。一些規(guī)則的名相當(dāng)于相應(yīng)的頭字段名字。這樣的規(guī)則表示相應(yīng)的頭字段值,例如Sec-WebSocket-Key ABNF規(guī)則描述了|Sec-WebSocket-Key| 頭字段值的語法。在名字中帶有“-Client”后綴的ABNF規(guī)則僅用在由客戶端到服務(wù)器發(fā)送請求的情況;在名字中帶有“-Server”后綴的ABNF規(guī)則僅用在由服務(wù)器到客戶端發(fā)生響應(yīng)的情況。例如,ABNF規(guī)則Sec-WebSocket-Protocol-Client描述了客戶端到服務(wù)器發(fā)送的|Sec-WebSocket-Protocol|頭字段值的語法。
以下新的頭字段可以在從客戶端到服務(wù)器握手期間被發(fā)送:
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) ]
; When using the quoted-string syntax variant, the value
; after quoted-string unescaping MUST conform to the
; 'token' ABNF.
NZDIGIT = "1" | "2" | "3" | "4" | "5" | "6" |
"7" | "8" | "9"
version = DIGIT | (NZDIGIT DIGIT) |
("1" DIGIT DIGIT) | ("2" DIGIT DIGIT)
; Limited to 0-255 range, with no leading zeros
以下新的頭字段可以在服務(wù)器到客戶端握手期間被發(fā)送:
Sec-WebSocket-Extensions = extension-list
Sec-WebSocket-Accept = base64-value-non-empty
Sec-WebSocket-Protocol-Server = token
Sec-WebSocket-Version-Server = 1#version
本節(jié)提供了在客戶端和服務(wù)器中支持多個版本的WebSocket協(xié)議的一些指導(dǎo)。
使用WebSocket版本通知能力(|Sec-WebSocket-Version|頭字段),客戶端可以初始請求它選擇的WebSocket協(xié)議的版本(這并不一定必須是客戶端支持的最新的)。如果服務(wù)器支持請求的版本且握手消息是本來有效的,服務(wù)器將接受該版本。如果服務(wù)器不支持請求的版本,它必須以一個包含所有它將使用的版本的|Sec-WebSocket-Version|頭字段(或多個|Sec-WebSocket-Version|頭字段)來響應(yīng)。此時,如果客戶端支持一個通知的版本,它可以使用新的版本值重做WebSocket握手。
以下示例演示了上述的版本協(xié)商。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 25
服務(wù)器的響應(yīng)可能看起來像如下:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13, 8, 7
注意服務(wù)器最后的響應(yīng)可能也看起來像:
HTTP/1.1 400 Bad Request
...
Sec-WebSocket-Version: 13
Sec-WebSocket-Version: 8, 7
客戶端現(xiàn)在可以重做符合版本13的握手:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
...
Sec-WebSocket-Version: 13
更多建議: