前端WebSocket知識點總結

最近研究了下WebSocket,總結下目前對WebSocket的認知。本文不是基於WebSocket展開的一個從0到1的詳細介紹。若是你歷來沒有了解過WebScoket,建議能夠先搜一些介紹WebSocket的文章,這類文章仍是挺多的,我就再也不贅述了。html

下面的內容是基於你對WebSocket有基本瞭解後展開的幾個小的知識點:node

  1. ping/pong協議;
  2. 如何使ERROR_INTERNET_DISCONNECTED錯誤信息不顯示在控制檯;

ping/pong協議

背景:鏈接WebSocket的時候,發現WebSocket剛鏈接上沒過多久就斷開了,爲了保持長時間的鏈接,就想到了ping/pong協議。git

問題:github

  1. ping/pong是一種特殊的幀類型嗎,仍是說只是一種設計思想?
  2. JS有原生方法支持發送ping/pong消息嗎

經過WebSocket協議,發現ping/pong確實是一種特殊的幀類型:web

The Ping frame contains an opcode of 0x9.
The Pong frame contains an opcode of 0xA.

那麼,上面所說的opcode又是什麼東西呢?講opcode就得說到幀數據格式
image.png
經過上圖能夠發現,除了最後面的Payload Data,也就是咱們要發送的數據以外,還會有一些其餘信息。我以爲能夠類比http請求的請求頭部分。上圖中第5-8位表示的就是opcode的內容。其他字段的含義能夠參考上述WebSocket規範,或者搜WebSocket協議數據幀格式,這類博客仍是挺多的。設計模式

拿nodeJS舉個例子:
在瀏覽器端發起WebSocket的時候,會發送一個http請求,注意請求頭裏面的Upgrade字段,意思就是我要升級到websocket鏈接:瀏覽器

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

此時,nodeJS就能夠監聽upgrade事件,去作拒絕或者升級操做,注意下這個事件裏面有個參數socket:websocket

socket: <stream.Duplex> Network socket between the server and client

socket有一個write方法,該方法是能夠用來寫幀數據的,也就是上面幀格式裏面的所有數據,而不只僅是Payload Data。socket

ws倉庫就是使用了socket的write方法發送了根據WebSocket協議定義的ping/pong,部分關鍵代碼以下:函數

doPing(data, mask, readOnly, cb) {
  this.sendFrame(
    Sender.frame(data, {
      fin: true,
      rsv1: false,
      opcode: 0x09, // ping opcode
      mask,
      readOnly
    }),
    cb
  );
}
doPong(data, mask, readOnly, cb) {
  this.sendFrame(
    Sender.frame(data, {
      fin: true,
      rsv1: false,
      opcode: 0x0a, // pong opcode
      mask,
      readOnly
    }),
    cb
  );
}
sendFrame(list, cb) {
  if (list.length === 2) {
    this._socket.cork();
    this._socket.write(list[0]);
    this._socket.write(list[1], cb);
    this._socket.uncork();
  } else {
    this._socket.write(list[0], cb);
  }
}

因此,nodeJS是能夠實現WebSocket協議定義的ping/pong幀的。緣由是咱們能夠拿到socket對象,而且該對象提供了能夠發送完整幀數據的方法。那麼瀏覽器端呢?

瀏覽器提供了原生的WebSocket構造函數用來建立一個WebSocket實例,該實例只提供了一個send方法,而且該send方法只能用來發送上述協議中Payload Data的內容,瀏覽器會根據send的參數自動生成一個完整的幀數據。因此,在瀏覽器端是無法控制除了Payload Data以外的幀內容的,也就是沒法自定義opcode。因此,也就實現不了WebSocket規範定義的ping/pong協議。

此時,咱們就能夠把ping/pong當成一種用來解決特定問題的設計模式。既然咱們只能自定義Payload Data的內容,那麼咱們能夠簡單的在Payload Data裏面添加一個字段用於區分是ping/pong幀,仍是普通的數據幀,好比type。當type字段是ping/pong的時候代表是ping/pong幀,若是是其餘字段纔是普通的數據幀。

如何使ERROR_INTERNET_DISCONNECTED錯誤信息不顯示在控制檯

當斷網的時候,鏈接WebSocket會發現瀏覽器控制檯會log一個錯誤信息:

WebSocket connection to 'ws://...' failed: Error in connection establishment: net::ERR_INTERNET_DISCONNECTED

原先的開發經驗是,控制檯若是有報錯的話,確定是代碼某個地方有錯誤,而且沒有被咱們的代碼捕獲到,因此就會在控制檯拋出,若是使用了try catch 或者全局的window.onerror捕獲到了錯誤信息,就不會在控制檯打印了。因此,我就嘗試了上述方法,發現捕捉不到,仍是會在控制檯log。

另外,WebSocket提供了兩個事件,onerror和onclose。當發生上述錯誤信息的時候,onerror和onclose是會被調用的。可是,此時控制檯仍是會有上述報錯信息。

通過一番查找,發現沒法阻止上述錯誤信息顯示在控制檯。

那麼,爲何瀏覽器會設計這樣的行爲呢?猜想緣由以下:
上面說到經過onerror和onclose事件是能夠捕捉到WebSocket建立失敗的,可是,查看這兩個事件的參數,咱們只能從中找到一個code是1006的屬性,輸出在控制檯的錯誤信息ERR_INTERNET_DISCONNECTED在參數裏面找不到。接着,看一下code1006相關的東西:

User agents must not convey any failure information to scripts in a way that would allow a script to distinguish the following situations:

*   A server whose host name could not be resolved.
*   A server to which packets could not successfully be routed.
*   A server that refused the connection on the specified port.
*   A server that failed to correctly perform a TLS handshake (e.g., the server certificate can't be verified).
*   A server that did not complete the opening handshake (e.g. because it was not a WebSocket server).
*   A WebSocket server that sent a correct opening handshake, but that specified options that caused the client to drop the connection (e.g. the server specified a subprotocol that the client did not offer).
*   A WebSocket server that abruptly closed the connection after successfully completing the opening handshake.

In all of these cases, the the WebSocket connection close code would be 1006, as required by WebSocket Protocol. 

Allowing a script to distinguish these cases would allow a script to probe the user's local network in preparation for an attack.

從上述規範能夠看到,規範是禁止瀏覽器向腳本傳遞下述形成WebSocket鏈接失敗的具體緣由的,只容許向腳本傳遞一個1006的code碼,不然,用戶就能夠探測到局部網的信息,進而發起攻擊。舉個例子,上面那種斷網的狀況,腳本中只能獲得1006的狀態碼,好比下面這種報錯

Error in connection establishment: net::ERR_CONNECTION_REFUSED

也只能從onerror中得到一個1006的code碼。

因此,做爲開發人員,瀏覽器要怎麼在告訴咱們具體的錯誤信息的同時又阻止有可能發生的攻擊呢?答案就是在控制檯把具體的錯誤信息log出來。

總結

基於目前瞭解的知識總結的一篇博客,若有錯誤,歡迎留言討論。

相關文章
相關標籤/搜索