# 打開階段握手
## 4.1.客戶端要求
要*建立WebSocket連接*,客戶端打開一個連接并發送一個握手,就像本節中定義那樣。一個連接最初被定義為一個CONNECTING狀態。客戶端將需要提供一個/host/、/port/、/resource name/、和/secure/標記,它們都是在第三章討論的WebSocket URI的組件,連同一起使用的一個/protocols/和/extensions/列表。此外,如果客戶端是一個web瀏覽器,它提供/origin/。客戶端運行在一個受控環境,例如綁定到特定運營商的手機上的瀏覽器,可以下移(offload)連接管理到網絡上的另一個代理。在這種情況下,用于本規范目的的客戶端被認為包括手機軟件和任何這樣的代理。
當客戶端要*建立一個WebSocket連接*,給定一組(/host/、/port/、/resource name/、和/secure/標記)、連同一起使用的一個/protocols/和/extensions/列表、和在web瀏覽器情況下的一個/origin/,它必須打開一個連接、發送一個打開階段握手、并讀取服務器響應中的握手。應如何打開連接的確切要求、在打開階段握手應發送什么、以及應如何解釋服務器響應,在本節如下所述。在下面的文本中,我們將使用第三章的術語,如定義在那章的“/host” 、和“/sucure/標記” 。
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同時掛起連接,但如果30個同時連接到同一個請求的主機,那可能是不允許的)。例如,在一個web瀏覽器上下文中,客戶端需要考慮用戶已經打開的標簽數量,在設置同時掛起的連接數量的限制時。
注意:這使得它很難僅通過打開大量的WebSocket連接到遠程主機為腳本執行一個拒絕服務攻擊。當攻擊在關閉連接之前被暫停時,服務器可以進一步降低自身的負載,因為這將降低客戶端重新連接的速度。
注意:沒有限制一個客戶端可以與單個遠程主機有的已建立的WebSocket連接數量。服務器可以拒絕接受來自具有大量的現有連接的主機/IP地址的連接或當遭受高負載時斷開占用資源的連接。
3. *使用代理*:當有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=
~~~
如果客戶端沒有配置使用一個代理,那么應該打開一個直接TCP連接到由/host/給定的主機和/port/給定的端口。
注意:不暴露明確的UI來為WebSocket連接選擇一個獨立于其他代理的代理實現,鼓勵使用SOCKS5[[RFC1928](http://tools.ietf.org/html/rfc1928)]代理用于WebSocket連接,如果有的話,或做不到這一點,選擇為HTTPS連接配置代理勝過為HTTP連接配置代理。
為了代理自動配置腳本,傳給函數的URI必須從/host/、/port/、/resource name/、和/secure/標記來構造,使用第三章給定的WebSocket URI定義。
注意:WebSocket協議可以在代理自動配置腳本中從模式中識別(“ws”用于未加密的連接和“wss”用于加密的連接)。
4. 如果連接無法打開,或者因為直接連接失敗或者因為任何使用的代理返回一個錯誤,那么客戶端必須*失敗WebSocket連接*并終止連接嘗試。
5. 如果/secure/是true,客戶端必須在連接之上執行一個TLS握手在打開連接之后和發生握手數據之前[[RFC2818](http://tools.ietf.org/html/rfc2818)]。如果這個失敗了(例如,服務器的證書不能被驗證),那么客戶端必須*失敗WebSocket連接*并終止連接。否則,所有在該通道上的進一步的通信必須通過加密隧道[[RFC5246](http://tools.ietf.org/html/rfc5246)]。
客戶端必須在TLS握手中使用服務器命名指示(Server Name Indication)擴展[[RFC6066](http://tools.ietf.org/html/rfc6066)]。
一旦一個到服務器的連接連接(包括通過代理或在TLS加密隧道之上的連接),客戶端必須發送一個打開階段握手到服務器。該握手包括一個HTTP Upgrade請求,連同一個必需的和可選的頭字段列表。該握手的要求如下所示。
1. 握手必須是像[[RFC2616](http://tools.ietf.org/html/rfc6066)]指定的那樣的有效的HTTP請求。
2. 請求方法必須是GET、且HTTP版本必須是至少1.1。
例如,如果WebSocket URI是“ws://example.com/chat”,發送的第一行應該是“GET /chat HTTP/1.1”。
3. 請求的“Request-URI”部分必須匹配定義在第三章的(一個相對URI)/resource name/或是一個絕對的http/https URI,當解析時,有一個/resource name/、/host/、和/port/匹配相應的ws/wss URI。
4. 請求必須包含一個|Host|頭字段,其值包含/host/加上可選的“:”后跟/port/(當沒用默認端口時)。
5. 請求必須包含一個|Upgrade|頭字段,其值必須包含“websocket”關鍵字。
6. 請求必須包含一個|Connection|頭字段,其值必須包含“Upgrade”標記。
7. 請求必須包含一個名字為| Sec-WebSocket-Key |的頭字段,這個頭字段的值必須是臨時(nonce)組成的一個隨機選擇的已經base64編碼的(參見[[RFC4648](http://tools.ietf.org/html/rfc4648#section-4)]第4章)16位的值。臨時必須是為每個連接隨機選擇的。
注意:作為一個例子,如果隨機選擇的值是字節序列0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10,頭字段的值必須是“AQIDBAUGBwgJCgsMDQ4PEC==”
8. 如果請求來自一個瀏覽器客戶端,請求必須包含一個名字為|Origin|[RFC6454]的頭字段。如果連接是來自非瀏覽器客戶端,如果該客戶端的語義匹配描述在這的用于瀏覽器的使用情況時,請求可以包含這個字段。該頭字段的值是在建立正運行的連接代碼中的環境的origin 的ASCII序列化。參考[[RFC6454](http://tools.ietf.org/html/rfc6454)]獲取如果構造該頭字段的值的詳細信息。
作為一個例子,如果從www.example.com下載的代碼試圖建立到ww2.example.com的連接,該頭字段的值將是“[http://www.example.com”。](http://www.example.xn--com-9o0a./)
9. 請求必須包含一個名字為|Sec-WebSocket-Version|的頭字段。該頭字段的值必須是13。
注意:盡管本文檔的草案版本(-09、-10、-11、和-12)發布了(它們多不是編輯上的修改和澄清而不是改變電報協議[wire protocol]),值9、10、11、和12不被用作有效的Sec-WebSocket-Version。這些值被保留在IANA注冊中心,但并將不會被使用。
10. 請求可以包含一個名字為|Sec-WebSocket-Protocol|的頭字段。如果存在,該值表示一個或多個逗號分割的客戶端想要表達的子協議,按優先順序排列。包含該值的元素必須是非空字符串,且字符在U+0021到U+007E范圍內但不包含定義在[[RFC2616](http://tools.ietf.org/html/rfc2616)]中的分割字符且必須所有是唯一的字符串。用于該頭字段值的ABNF是1#token,其構造和規則定義在[[RFC2616](http://tools.ietf.org/html/rfc2616)]給出。
11. 請求可以包含一個名字為|Sec-WebSocket-Extensions|的頭字段。如果存在,該值表示客戶端想要表達的協議級的擴展。該頭字段的解釋和格式描述在第9.1節。
12. 請求可以包含任意其他頭字段,例如,cookie[[RFC6265](http://tools.ietf.org/html/rfc6265)]和/或驗證相關的頭字段例如|Authorization|頭字段[[RFC2616](http://tools.ietf.org/html/rfc2616)],其根據定義它們的文檔處理。
一旦客戶端的打開階段握手已經發送,客戶端在發送任何進一步數據之前必須等待自服務器的一個響應。客戶端必須驗證服務器的響應,如下所示:
1. 如果收到的服務器的狀態碼不是101,客戶端處理每個HTTP[[RFC2616](http://tools.ietf.org/html/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”但忽略任何前導和結尾空格相關聯的base64編碼的SHA-1值,客戶端必須*失敗WebSocket連接*。
5. 如果響應包含一個|Sec-WebSocket-Extensions|頭字段且此頭字段表示使用一個擴展但沒有出現在客戶端握手中(服務器表示的一個擴展,不是客戶端請求的),客戶端必須*失敗WebSocket連接*。解析該頭字段以確定請求了哪些擴展在9.1節討論。
6. 如果響應包含一個|Sec-WebSocket-Protocol|頭字段且該頭字段表示使用一個子協議但沒出現在客戶端握手中(服務器表示的一個子協議,不是客戶端請求的),客戶端必須*失敗WebSocket連接*。
如果服務器響應不符合定義在本節和4.2.2節中的服務器握手的要求,客戶端必須*失敗WebSocket連接*。
請注意,根據[[RFC2616](http://tools.ietf.org/html/rfc2616)],所有命名在HTTP請求和HTTP響應中的頭字段是不區分大小寫的。
如果服務器響應驗證了以上提供的,這是說,*WebSocket連接建立了*且WebSocket連接處于OPEN狀態。*使用中的擴展*被定義為一個(可能空的)字符串,其值等于服務器握手中提供的|Sec-WebSocket-Extensions|頭字段的值或如果在服務器握手中沒有該頭字段則為null值。*使用中的子協議*被定義為服務器握手中的|Sec-WebSocket-Protocol|頭字段的值或如果在服務器握手中沒有該頭字段則為null值。另外,如果在服務器握手中表示cookie應該被設置(定義在[[RFC6265](http://tools.ietf.org/html/rfc6265)])的任何頭字段,這些cookie被稱為*在服務器打開階段握手期間的Cookie設置*。
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B#42%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E8%A6%81%E6%B1%82)4.2.服務器端要求
服務器可以下移(offload)連接管理到網絡上的其他代理,例如,負載均衡和反向代理。在這樣的情況下,用于本規范的目的的服務器被認為是包括服務器端基礎設施的所有部分,從開始的設備到終止TCP連接,處理請求和發送響應的服務器的所有方式。
例如:一個數據中心可能有一個用適當的握手來響應WebSocket請求的服務器,并接著傳遞連接到另一個服務器來真正處理數據幀。對于本規范的目的,“服務器”是結合了兩種計算機。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B#421%E8%AF%BB%E5%8F%96%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B)4.2.1.讀取客戶端的打開階段握手
當客戶端開始一個WebSocket連接,它發送它的打開階段握手部分。服務器必須至少解析這個握手為了獲取必要的信息來生成服務器握手部分。
客戶端打開階段握手包括以下部分。如果服務器,當讀取握手時,發現客戶端沒有發送一個匹配下面描述的握手(注意,按照[[RFC2616](http://tools.ietf.org/html/rfc2616)],頭字段順序是不重要的),包括但不限制任何違反ABNF語法指定的握手組件,服務器必須停止處理客戶端握手并返回一個具有一個適當錯誤碼的(例如400錯誤的請求)HTTP響應。
1. 一個HTTP/1.1或更高版本的GET請求,包括一個“Request-URI” [[RFC2616](http://tools.ietf.org/html/rfc2616)]應該被解釋為定義在第3章的/resource name/(或一個包含/resource name/的絕對HTTP/HTTPS URI)。
2. 一個|Host|頭字段包含服務器的權限。
3. 一個|Upgrade|頭字段包含值“websocket”,視為一個不區分大小寫的ASCII值。
4. 一個|Connection|頭字段包含符號“Upgrade”,視為一個不區分大小寫的ASCII值。
5. 一個|Sec-WebSocket-Key|頭字段,帶有一個base64編碼的值(參見[[RFC4648](http://tools.ietf.org/html/rfc4648#section-4)]第4章),當解碼時,長度是16字節。
6. 一個|Sec-WebSocket-Version|頭字段,帶有值13。
7. 可選的,一個|Origin|頭字段。該頭字段由所有瀏覽器客戶端發送。一個試圖缺失此頭字段的連接不應該被解釋為來自瀏覽器客戶端。
8. 可選的,一個|Sec-WebSocket-Protocol|頭字段,帶有表示客戶端想要表達的協議的值列表,按優先順序排列。
9. 可選的,一個|Sec-WebSocket-Extensions|頭字段,帶有表示客戶端想要表達的擴展的值列表。此頭字段的解釋在9.1節討論。
10. 可選的,其他頭字段,例如這些用于發送cookie或請求服務器身份驗證的。未知的頭字段被忽略,按照[[RFC2616](http://tools.ietf.org/html/rfc2616)]。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B#422%E5%8F%91%E9%80%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B)4.2.2.發送服務器的打開階段握手
當客戶端建議一個到服務器的WebSocket連接,服務器必須完成以下步驟來接受該連接并發送服務器的打開階段握手。
1. 如果連接發送在一個HTTPS (HTTP-over-TLS)端口上,在連接之上執行一個TLS握手。如果失敗了(例如,在擴展的客戶端hello“server_name”擴展中的客戶端指示的一個主機名,服務器對主機不可用),則關閉該連接;否則,用于該連接的所有進一步的通信必須貫穿加密的隧道[[RFC5246](http://tools.ietf.org/html/rfc5246)]。
2. 服務器可以執行額外的客戶端身份認證,例如,返回401狀態碼與描述在[[RFC2616](http://tools.ietf.org/html/rfc2616)]中的相關的|WWW-Authenticate|頭字段。
3. 服務器可以使用3xx狀態碼[[RFC2616](http://tools.ietf.org/html/rfc2616)]重定向客戶端。注意,此步驟可能連同,之前,或之后的可選的上面描述的身份驗證步驟一起發生。
4. 建立如下信息:
/origin/
客戶端握手中的|Origin|頭字段表示建立連接的腳本的來源。Origin是序列化為ASCII并轉換為小寫。服務器可以使用這個信息作為決定是否接受傳入連接的一部分。如果服務器沒有驗證origin,它將接受來自任何地方的連接。如果服務器不想接受這個連接,它必須返回一個適當的HTTP錯誤碼(例如,403 Forbidden)并中斷描述在本章中的WebSocket握手。更多詳細信息,請參閱第10章。
/key/
在客戶端握手中的|Sec-WebSocket-Key|頭字段包括一個base64編碼的值,如果解碼,長度是16字節。這個(編碼的)值用在創建服務器握手時來表示接受連接。服務器沒必要使用base64解碼|Sec-WebSocket-Key|值。
/version/
客戶端握手中的|Sec-WebSocket-Version|頭字段包括客戶端試圖通信的WebSocket協議的版本。如果該版本沒有匹配服務器理解的一個版本,服務器必須中斷描述在本節的WebSocket握手并替代返回一個適當的HTTP錯誤碼(例如,426 Upgrade Required)且一個|Sec-WebSocket-Version|頭字段表示服務器能理解的版本。
/resource name/
由服務器提供的服務的標識符。如果服務器提供多個服務,那么該值應該源自客戶端我手中的GET方法的“Request-URI”中給定的資源名。如果請求的服務器不可用,服務器必須發生一個適當的HTTP錯誤碼(例如404 NotFound)并中斷WebSocket握手。
/subprotocol/
或者一個代表服務器準備使用的子協議的單個值或者null。選擇的值必須源自客戶端握手,從|Sec-WebSocket-Protocol|字段具體地選擇一個值,服務器將使用它用于這個連接(如果有)。如果客戶端握手不包含這樣一個頭字段或如果服務器不同意任何客戶端請求的子協議,僅接受的值為null。這個字段不存在等價于null值(意思是如果服務器不想同意任何建議的子協議,它必須在它的響應中不發送回一個|Sec-WebSocket-Protocol|頭字段)。用于這些目的,空字符串與null值是不一樣的,且它不是這個字段合法的值。用于該頭字段的值ABNF是(符號),構造定義和規則在[RFC2616]中給出。
/extensions/
表示服務器準備使用的協議級別擴展的一個列表(可能為空)。如果服務器支持多個擴展,那么該值必須源自客戶端握手,通過從|Sec-WebSocket-Extensions|字段具體地選擇一個或多個值。這個字段不存在等價于null值。用于這些目的,空字符串與null值是不一樣的。客戶未列出的擴展必須不被列出。那些值應該被選擇和解釋的方法在9.1節討論。
1. 如果服務器選擇接受傳入的連接,它必須以一個有效的表示以下的HTTP響應應答。
2. 一個按照RFC2616[RFC2616]帶有101響應碼的Status-Line。這樣的響應可能看起來像“HTTP/1.1 101 Switching Protocols”。
3. 一個按照RFC2616[RFC2616]帶有值“websocket”的|Upgrade|頭字段。
4. 一個帶有“Upgrade”的| Connection |頭字段。
5. 一個|Sec-WebSocket-Accept|頭字段。該頭字段的值通過連接/key/構造,它定義在4.2.2節第4步,帶有字符串“258EAFA5- E914-47DA-95CA-C5AB0DC85B11”,采用SHA-1散列這個連接的值來獲取一個20字節的值并base64編碼(參考[RFC4648]第4章)這個20字節的散列。
該頭字段的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|頭字段,帶有一個定義在4.2.2節第4步的值/subprotocol/。
2. 可選的,一個|Sec-WebSocket-Extensions|頭字段,帶有一個定義在4.2.2節第4步的值/ extensions /。如果使用多個擴展,那么可以把所有都列在一個|Sec-WebSocket-Extensions|頭字段中或者分配到|Sec-WebSocket-Extensions|頭字段的多個實例之間。 這就完成了服務器握手。如果服務器完整這些步驟且沒有中斷WebSocket握手,服務器認為WebSocket連接已建立且WebSocket連接處于OPEN狀態。此時,服務器可以開始發送(和接收)數據了。
### [](https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B#43%E4%B8%BA%E6%8F%A1%E6%89%8B%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%9A%84%E6%96%B0%E7%9A%84%E5%A4%B4%E5%AD%97%E6%AE%B5%E6%95%B4%E7%90%86%E7%9A%84abnf)4.3.為握手中使用的新的頭字段整理的ABNF
本節使用的ABNF語法/規范來自[RFC2616]第2.1節,包括“隱式*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) ]
; 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
~~~
以下新的頭字段可以在服務器到客戶端握手期間被發送:
~~~
Sec-WebSocket-Extensions = extension-list
Sec-WebSocket-Accept = base64-value-non-empty
Sec-WebSocket-Protocol-Server = token
Sec-WebSocket-Version-Server = 1#version
~~~
## [](https://github.com/zhangkaitao/websocket-protocol/wiki/4.%E6%89%93%E5%BC%80%E9%98%B6%E6%AE%B5%E6%8F%A1%E6%89%8B#44%E6%94%AF%E6%8C%81%E5%A4%9A%E4%B8%AA%E7%89%88%E6%9C%AC%E7%9A%84websocket%E5%8D%8F%E8%AE%AE)4.4.支持多個版本的WebSocket協議
本節提供了在客戶端和服務器中支持多個版本的WebSocket協議的一些指導。
使用WebSocket版本通知能力(|Sec-WebSocket-Version|頭字段),客戶端可以初始請求它選擇的WebSocket協議的版本(這并不一定必須是客戶端支持的最新的)。如果服務器支持請求的版本且握手消息是本來有效的,服務器將接受該版本。如果服務器不支持請求的版本,它必須以一個包含所有它將使用的版本的|Sec-WebSocket-Version|頭字段(或多個|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
~~~