本節(jié)是非規(guī)范的。
過(guò)去,創(chuàng)建需要在客戶端和服務(wù)之間雙向通信(例如,即時(shí)消息和游戲應(yīng)用)的web應(yīng)用, 需要一個(gè)濫用的HTTP來(lái)輪詢(xún)服務(wù)器進(jìn)行更新但以不同的HTTP調(diào)用發(fā)生上行通知[RFC6202]。
這將導(dǎo)致各種各樣的問(wèn)題:
o 服務(wù)器被迫為每個(gè)客戶端使用一些不同的底層TCP連接: 一個(gè)用于發(fā)送信息到客戶端和一個(gè)新的用于每個(gè)傳入消息。
o 線路層協(xié)議有較高的開(kāi)銷(xiāo),因?yàn)槊總€(gè)客戶端-服務(wù)器消息都有一個(gè)HTTP頭信息。
o 客戶端腳本被迫維護(hù)一個(gè)傳出的連接到傳入的連接的映射來(lái)跟蹤回復(fù)。
一個(gè)簡(jiǎn)單的辦法是使用單個(gè)TCP連接雙向傳輸。這是為什么提供WebSocket 協(xié)議。與WebSocket API結(jié)合[WSAPI],它提供了一個(gè)HTTP輪詢(xún)的替代來(lái)進(jìn)行從web 頁(yè)面到遠(yuǎn)程服務(wù)器的雙向通信。 同樣的技術(shù)可以用于各種各樣的web應(yīng)用:
游戲、股票行情、同時(shí)編輯的多用戶應(yīng)用、服務(wù)器端服務(wù)以實(shí)時(shí)暴露的用戶接口、等等。
WebSocket協(xié)議被設(shè)計(jì)來(lái)取代現(xiàn)有的使用HTTP作為傳輸層的雙向通信技術(shù),并受益于現(xiàn)有的基礎(chǔ)設(shè)施(代理、過(guò)濾、身份驗(yàn)證)。這樣的技術(shù)被實(shí)現(xiàn)來(lái)在效率和可靠性之間權(quán)衡,因?yàn)镠TTP最初目的不是用于雙向通信(參見(jiàn)[RFC6202]的進(jìn)一步討論)。WebSocket協(xié)議試圖在現(xiàn)有的HTTP基礎(chǔ)設(shè)施上下文中解決現(xiàn)有的雙向HTTP技術(shù)目標(biāo);同樣,它被設(shè)計(jì)工作在HTTP端口80和443,也支持HTTP代理和中間件,即使這具體到當(dāng)前環(huán)境意味著一些復(fù)雜性。但是,這種設(shè)計(jì)不限制WebSocket到HTTP,且未來(lái)的實(shí)現(xiàn)可以在一個(gè)專(zhuān)用的端口上使用一個(gè)更簡(jiǎn)單的握手,且沒(méi)有再創(chuàng)造整個(gè)協(xié)議。最后一點(diǎn)是很重要的,因?yàn)榻换ハ⒌膫鬏斈J讲痪_地匹配標(biāo)準(zhǔn)HTTP傳輸并可能在相同組件上包含不常見(jiàn)的負(fù)載。
本節(jié)是非規(guī)范的。
本協(xié)議有兩部分:握手和數(shù)據(jù)傳輸。
來(lái)自客戶端的握手看起來(lái)像如下形式:
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
來(lái)自服務(wù)器的握手看起來(lái)像如下形式:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
來(lái)自客戶端的首行遵照Request-Line格式。
來(lái)自服務(wù)器的首行遵照Status-Line格式。
Request-Line 和 Status-Line 制品定義在[RFC2616]。
在這兩種情況中一個(gè)無(wú)序的頭字段集合出現(xiàn)在首行之后。這些頭字段的意思指定在本文檔的第4章。另外的頭字段也可能出現(xiàn),例如cookies[RFC6265]。頭的格式和解析定義在[RFC2616]。
一旦客戶端和服務(wù)器都發(fā)送了它們的握手,且如果握手成功,接著開(kāi)始數(shù)據(jù)傳輸部分。這是一個(gè)每一端都可以的雙向通信信道,彼此獨(dú)立,隨意發(fā)生數(shù)據(jù)。
一個(gè)成功握手之后,客戶端和服務(wù)器來(lái)回地傳輸數(shù)據(jù),在本規(guī)范中提到的概念單位為“消息”。在線路上,一個(gè)消息是由一個(gè)或多個(gè)幀的組成。WebSocket的消息并不一定對(duì)應(yīng)于一個(gè)特定的網(wǎng)絡(luò)層幀,可以作為一個(gè)可以被一個(gè)中間件合并或分解的片段消息。
一個(gè)幀有一個(gè)相應(yīng)的類(lèi)型。屬于相同消息的每一幀包含相同類(lèi)型的數(shù)據(jù)。從廣義上講,有文本數(shù)據(jù)類(lèi)型(它被解釋為UTF-8[RFC3629]文本)、二進(jìn)制數(shù)據(jù)類(lèi)型(它的解釋是留給應(yīng)用)、和控制幀類(lèi)型(它是不準(zhǔn)備包含用于應(yīng)用的數(shù)據(jù),而是協(xié)議級(jí)的信號(hào),例如應(yīng)關(guān)閉連接的信號(hào))。這個(gè)版本的協(xié)議定義了六個(gè)幀類(lèi)型并保留10以備將來(lái)使用。
本節(jié)是非規(guī)范的。
打開(kāi)階段握手目的是兼容基于HTTP的服務(wù)器軟件和中間件,以便單個(gè)端口可以用于與服務(wù)器交流的HTTP客戶端和與服務(wù)器交流的WebSocket客戶端。最后,WebSocket客戶端的握手是一個(gè)HTTP Upgrade請(qǐng)求:
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],握手中的頭字段可能由客戶端按照任意順序發(fā)送,因此在接收的不同頭字段中的順序是不重要的。
“Request-URI”的GET方法[RFC2616]用于識(shí)別WebSocket連接的端點(diǎn),即允許從一個(gè)IP地址服務(wù)的多個(gè)域名,也允許由單臺(tái)服務(wù)器服務(wù)的多個(gè)WebSocket端點(diǎn)。 客戶端按照[RFC2616]在它的握手的|Host|頭字段中包含主機(jī)名,以便客戶端和服務(wù)器都都能驗(yàn)證他們同意哪一個(gè)正在使用的主機(jī)。
在WebSocket協(xié)議中另外的頭字段可以用于選擇選項(xiàng)。典型的選項(xiàng)在這個(gè)版本中可用的是子協(xié)議選擇器(|Sec-WebSocket-Protocol|)、客戶端支持的擴(kuò)展列表(|Sec-WebSocket-Extensions|)、|Origin|頭字段等。|Sec-WebSocket-Protocol|請(qǐng)求頭字段可以用來(lái)表示客戶端接受的子協(xié)議(WebSocket協(xié)議上的應(yīng)用級(jí)協(xié)議層)。服務(wù)器選擇一個(gè)可接受的協(xié)議或不,并在它的握手中回應(yīng)該值表示它已經(jīng)選擇了那個(gè)協(xié)議。
Sec-WebSocket-Protocol: chat
|Origin|頭字段[RFC6454]是用于保護(hù)防止未授權(quán)的被瀏覽器中的使用WebSocket API的腳本跨域使用WebSocket服務(wù)器。服務(wù)器收到WebSocket連接請(qǐng)求生成的腳本來(lái)源。如果服務(wù)器不想接受來(lái)自此來(lái)源的連接,它可以選擇通過(guò)發(fā)送一個(gè)適當(dāng)?shù)腍TTP錯(cuò)誤碼拒絕該連接。這個(gè)頭字段由瀏覽器客戶端發(fā)送,對(duì)于非瀏覽器客戶端,如果它在這些客戶端上下文中有意義,這個(gè)頭字段可以被發(fā)送。
最后,服務(wù)器要證明收到客戶端WebSocket握手的客戶端,以便服務(wù)器不接受不是WebSocket連接的連接。這可以防止一個(gè)通過(guò)使用XMLHttpRequest [XMLHttpRequest]或一個(gè)表單提交發(fā)送它精心制作的包欺騙WebSocket服務(wù)器的攻擊者。
為了證明收到的握手,服務(wù)器必須攜帶兩條信息并組合他們形成一個(gè)響應(yīng)。
第一條信息源自客戶端握手中的| Sec-WebSocket-Key |頭信息: Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
對(duì)于這個(gè)頭字段,服務(wù)器必須攜帶其值(出現(xiàn)在頭字段上,如,減去開(kāi)頭和結(jié)尾空格的base64-編碼[RFC4648]的版本)并將這個(gè)與字符串形式的全局唯一標(biāo)識(shí)符(GUID,[RFC4122])“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”連接起來(lái),其不太可能被不理解WebSocket協(xié)議的網(wǎng)絡(luò)端點(diǎn)使用。SHA-1散列(160位)[FIPS.180-3]、base-64編碼(參見(jiàn)[RFC4648]第4章)、用于這個(gè)的一系列相關(guān)事物接著在服務(wù)器握手過(guò)程中返回。
具體而言,如果在上面例子中,|Sec-WebSocket-Key|頭字段的值為“dGhlIHNhbXBsZSBub25jZQ==”,服務(wù)器將連接字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”形成字符串“dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。服務(wù)器接著使用SHA-1散列這個(gè),并產(chǎn)生值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。這個(gè)值接著使用base64編碼(參見(jiàn)[RFC4648]第4章),產(chǎn)生值“s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”。這個(gè)值將接著在|Sec-WebSocket-Accept|頭字段中回應(yīng)。
來(lái)自服務(wù)器的握手比客戶端握手更簡(jiǎn)單。首行是一個(gè)HTTP Status-Line,具有狀態(tài)碼101:
HTTP/1.1 101 Switching Protocols
101以外的任何狀態(tài)碼表示W(wǎng)ebSocket握手沒(méi)有完成且HTTP語(yǔ)義仍適用。頭信息遵照該狀態(tài)碼。
|Connection|和|Upgrade|頭字段完成HTTP升級(jí)。|Sec-WebSocket-Accept|頭字段表示服務(wù)器是否將接受該連接。如果存在,這個(gè)頭字段必須包括客戶端在|Sec-WebSocket-Key|中現(xiàn)時(shí)發(fā)送的與預(yù)定義的GUID的散列。任何其他值不能被解釋為一個(gè)服務(wù)器可接受的連接。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
這些字段由WebSocket客戶端為腳本頁(yè)面做檢查。如果|Sec-WebSocket-Accept|不能匹配盼望的值、如果頭字段缺失、或HTTP狀態(tài)碼不是101,則連接將不能建立,且WebSocket幀將不發(fā)生。
可選的字段也可以被包含在內(nèi)。在這合格版本的協(xié)議中,主要可選字段是|Sec-WebSocket-Protocol|,其表示服務(wù)器選擇的子協(xié)議。WebSocket客戶端驗(yàn)證服務(wù)器包含的在WebSocket客戶端握手中指定的一個(gè)值。聲明多個(gè)子協(xié)議的服務(wù)器必須確保它選擇一個(gè),基于客戶端握手并指定它在其握手中。
Sec-WebSocket-Protocol: chat
服務(wù)器也可以設(shè)置cookie相關(guān)的可選字段為_(kāi)set_cookies,描述在[RFC6265]。
本節(jié)是非規(guī)范的。
關(guān)閉階段握手比打開(kāi)階段握手簡(jiǎn)單得多。
兩個(gè)節(jié)點(diǎn)中的任一個(gè)都能發(fā)送一個(gè)控制幀與包含一個(gè)指定控制序列的數(shù)據(jù)來(lái)開(kāi)始關(guān)閉階段握手(詳見(jiàn)5.5.1節(jié))。在收到這樣一個(gè)幀時(shí),另一個(gè)節(jié)點(diǎn)在響應(yīng)中發(fā)送一個(gè)Close幀,如果還沒(méi)有發(fā)送一個(gè)。在收到那個(gè)控制幀時(shí),第一個(gè)節(jié)點(diǎn)接著關(guān)閉連接,安全地知道沒(méi)有更多的數(shù)據(jù)到來(lái)。
發(fā)送一個(gè)控制幀之后,表示連接將被關(guān)閉,一個(gè)節(jié)點(diǎn)不會(huì)發(fā)送任何更多的數(shù)據(jù);在接收到一個(gè)控制幀之后,表示連接將被關(guān)閉,一個(gè)節(jié)點(diǎn)會(huì)丟棄收到的任何更多的數(shù)據(jù)。
對(duì)于兩個(gè)節(jié)點(diǎn)同時(shí)地初始化這個(gè)握手是安全的。
關(guān)閉階段握手目的是完成TCP關(guān)閉握手(FIN/ACK),基于TCP關(guān)閉階段握手不總是可靠的端到端,尤其在存在攔截代理和中間件。 通過(guò)發(fā)送一個(gè)Close幀并等待響應(yīng)中的Close幀,某些情況下可避免數(shù)據(jù)不必要的丟失。例如,在某些平臺(tái)上,如果一個(gè)socket關(guān)閉了,且接收隊(duì)列中有數(shù)據(jù),一個(gè)RST包被發(fā)送了,這樣會(huì)導(dǎo)致接受RST的一方的recv()失敗,即使有數(shù)據(jù)等待讀取。
本節(jié)是非規(guī)范的。
WebSocket協(xié)議應(yīng)該以最小幀的原則設(shè)計(jì)(唯一存在的框架是使協(xié)議基于幀而不是基于流且支持區(qū)分Unicode文本和二進(jìn)制幀)。期望通過(guò)應(yīng)用層將元數(shù)據(jù)分層在WebSocket之上,同樣地,通過(guò)應(yīng)用層將元數(shù)據(jù)分層在TCP之上(例如,HTTP)。
從概念上講,WebSocket只是TCP之上的一層,執(zhí)行以下操作:
o 為瀏覽器添加一個(gè)web 基于來(lái)源的安全模型
o 添加一個(gè)尋址和協(xié)議命名機(jī)制來(lái)支持在一個(gè)IP地址的一個(gè)端口的多個(gè)主機(jī)名的多個(gè)服務(wù)
o 在TCP之上分一個(gè)幀機(jī)制層以回到TCP基于的IP包機(jī)制,但沒(méi)有長(zhǎng)度限制
o 包括一個(gè)額外的帶內(nèi)(in-band)關(guān)閉階段握手,其被設(shè)計(jì)來(lái)工作在現(xiàn)存的代理和其他中間件。
除此之外,WebSocket沒(méi)有添加任何東西?;旧?,它的目的是盡可能接近僅暴露原始TCP到腳本,盡可能考慮到Web的約束。它也被設(shè)計(jì)為它的服務(wù)器能與HTTP服務(wù)器共享一個(gè)端口的這樣一種方式,通過(guò)持有它的握手是一個(gè)有效的HTTP Upgrade請(qǐng)求。一個(gè)可以在概念上使用其他協(xié)議來(lái)建立客戶端-服務(wù)器消息,但WebSocket的意圖是提供一個(gè)相對(duì)簡(jiǎn)單的協(xié)議,可以與現(xiàn)有HTTP和部署的HTTP基礎(chǔ)設(shè)施(例如代理)同時(shí)存在,并盡可能接近TCP,且對(duì)于使用考慮到安全考慮的這樣的基礎(chǔ)設(shè)施同樣是安全的,有針對(duì)性的補(bǔ)充以簡(jiǎn)化使用并保持簡(jiǎn)單的事情簡(jiǎn)單(如增加的消息語(yǔ)義)。
協(xié)議的目的是為了可擴(kuò)展;未來(lái)版本將可能引入額外的概念如復(fù)用(multiplexing)。
本節(jié)是非規(guī)范的。
WebSocket協(xié)議使用瀏覽器使用的來(lái)源模型限制web頁(yè)面可以與WebSocket服務(wù)器通信,當(dāng)WebSocket協(xié)議是從一個(gè)web頁(yè)面使用。當(dāng)然,當(dāng)WebSocket協(xié)議被一個(gè)獨(dú)立的客戶端直接使用時(shí)(也就是,不是從瀏覽器中的一個(gè)web頁(yè)面),來(lái)源模型不再有用,因?yàn)榭蛻舳丝梢蕴峁┤我怆S意的來(lái)源字符串。
該協(xié)議的目的是無(wú)法與現(xiàn)有的協(xié)議如SMTP[RFC5321]和HTTP建立一個(gè)連接,同時(shí)允許HTTP服務(wù)器來(lái)選擇支持該協(xié)議如果想要。這是通過(guò)具有嚴(yán)格的和詳盡的握手和通過(guò)限制在握手完成之前能被插入到連接的數(shù)據(jù)(因此限制多少服務(wù)器可以被應(yīng)用)實(shí)現(xiàn)的。
當(dāng)數(shù)據(jù)是來(lái)自其他協(xié)議時(shí),同樣的目的是無(wú)法建立連接的,尤其發(fā)送到一個(gè)WebSocket服務(wù)器的HTTP,例如,如果一個(gè)HTML“表單”提交到WebScoket服務(wù)器可能會(huì)發(fā)生。這主要通過(guò)要求服務(wù)器驗(yàn)證它讀取的握手來(lái)實(shí)現(xiàn),它只能做 如果握手包含適當(dāng)?shù)牟糠?,只能通過(guò)一個(gè)WebScoket客戶端發(fā)送。尤其是,在寫(xiě)本規(guī)范的時(shí)侯,|Sec-|開(kāi)頭的字段不能由web瀏覽器的攻擊者設(shè)置,僅能使用HTML和JavaScript API,例如XMLHttpRequest [XMLHttpRequest]。
本節(jié)是非規(guī)范的。
WebSocket協(xié)議是一個(gè)獨(dú)立的基于TCP的協(xié)議。它與HTTP唯一的關(guān)系是它的握手是由HTTP服務(wù)器解釋為一個(gè)Upgrade請(qǐng)求。
默認(rèn)情況下,WebSocket協(xié)議使用端口80用于常規(guī)的WebSocket連接和端口443用于WebSocket連接的在傳輸層安全(TLS)[RFC2818]之上的隧道化。
本節(jié)是非規(guī)范的。
當(dāng)一個(gè)連接到一個(gè)HTTP服務(wù)器共享的端口時(shí)(這種情況是很可能在傳輸信息到端口80和443出現(xiàn)),連接將出現(xiàn)在HTTP服務(wù)器,是一個(gè)正常的具有一個(gè)Upgrade提議的GET請(qǐng)求。在相對(duì)簡(jiǎn)單的安裝,只用一個(gè)IP地址和單臺(tái)服務(wù)器用于所有數(shù)據(jù)傳輸?shù)絾蝹€(gè)主機(jī)名,這可能允許一個(gè)切實(shí)可行的辦法對(duì)基于WebSocket協(xié)議的系統(tǒng)進(jìn)行部署。在更復(fù)雜的安裝(例如,負(fù)載均衡和多服務(wù)器),一組獨(dú)立的用于WebSocket連接的主機(jī)從HTTP服務(wù)器分離出來(lái)可能更容易管理。在寫(xiě)該規(guī)范的時(shí)候,應(yīng)該指出的是,在端口80和443上的連接有明顯不同的成功率,對(duì)于在端口443上的連接是明顯更有可能成功,盡管這可能會(huì)隨著時(shí)間而改變。
本節(jié)是非規(guī)范的。
客戶端可能通過(guò)包含|Sec-WebSocket-Protocol|字段在它的握手中使用一個(gè)特定的子協(xié)議請(qǐng)求服務(wù)器。如果它被指定,服務(wù)器需要在它的響應(yīng)中包含同樣的字段和一個(gè)選擇的子協(xié)議值用于建立連接。
這些子協(xié)議名字應(yīng)該按照11.5節(jié)被注冊(cè)。為了避免潛在的碰撞,推薦使用包含ASCII版本的子協(xié)議發(fā)明人的域名的名字。 例如,如果Example公司要?jiǎng)?chuàng)建一個(gè)Chat子協(xié)議,由Web上的很多服務(wù)器實(shí)現(xiàn),它們可能命名它為“chat.example.com”。如果Example組織命名它們的競(jìng)爭(zhēng)子協(xié)議為“chat.example.org”,那么兩個(gè)子協(xié)議可能由服務(wù)器同時(shí)實(shí)現(xiàn),因?yàn)榉?wù)器根據(jù)客戶端發(fā)送的值動(dòng)態(tài)地選擇使用哪一個(gè)子協(xié)議。
通過(guò)改變子協(xié)議的名字,子協(xié)議可以以向后不兼容方式版本化,例如,要從“bookings.example.net”到“v2.bookings.example.net”。就WebSocket客戶端而言,這些子協(xié)議將被視為是完全不同的。向后兼容的版本可以通過(guò)重用相同的子協(xié)議字符串實(shí)現(xiàn),但要仔細(xì)設(shè)置實(shí)際的子協(xié)議以支持這種可擴(kuò)展性。
更多建議: