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