Websocket 封包分析與 CloudFront 握手失敗

今天在研究 Websocket 到底握手是怎麼握的。因為我發現有時候 WebSocket 可以成功的 Upgrade 並回應 status code 101 ,但有時候又會被認成一般的請求導致 Upgrade 沒有成功觸發。

今天就來徹底了解一下WebSocket 到底是怎麼觸發並建立的,與從 HTTP 升級到 WebSocket 協議有哪些好處。

好處

  • 除了握手之外沒有 HTTP headers,節省傳輸量
  • 雙邊都可以主動發送訊息

我覺得有點怪的是, WebSocket 反而是把一些東西精簡化了,變得很不像升級,反而比較像降級。

WebSocket 概覽

實際測試

我是用 node.js 弄了一個測試站。正常的 websocket 一開始會用 HTTP 進行握手。 Status code 101 表示握手成功。

GET / HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: R7OZj3J5zXhZW0q0Sottaw==

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 6uRmXgk0kl3hlFIo2wNAEUQgxY4=

從封包可以看出來,真的是 HTTP 請求。但如果沒有帶相關的 header ,就會有異常現象,回應 101 以外的狀態碼。其實就是被當成一般的 HTTP 請求來處理了,但我的 server 就沒有支援要返回一般的 HTTP 回應,所以會看到 426 錯誤碼。

GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.79.1
Accept: */*

HTTP/1.1 426 Upgrade Required
Content-Length: 16
Content-Type: text/plain
Date: Thu, 23 Mar 2023 15:27:56 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Upgrade Required

握手失敗的原因

掌握了 WebSocket handshake 大致原理,就可以比較好推測失敗原因了。要調查請求發過來的時候,到底帶了哪些 header 。我曾經遇過中間 CDN 會偷改 Connection header 的,當時是客戶端亂來,同時帶了兩個不同的 connection headers 。

以下我用一台 EC2 啟用 CloudFront 後面接 webhook.site 作為測試。雖然 webhook.site 不支援 WebSocket ,但他可以很方便捕捉我們想看到的 request headers ,來確認握手訊息是否正常 (殊不知有雷,後面會說)。

curl -v https://d2uldy09xinkw1.cloudfront.net/6657752b-bdb6-44e5-96c2-b35bb223f633 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: R7OZj3J5zXhZW0q0Sottaw==' --http1.1

以上可見 connection 與 upgrade 都正常。

但我發現 HTTP2 沒辦法讓 CloudFront 正常的進行 websocket handshake ,目前整個Websocket over HTTP/2 標準正在草稿階段。在 HTTP/2 的規範裡面,不允許 ConnectionUpgrade headers 的出現,因此 CloudFront 並沒有幫我們到源站進行 websocket handshake 。

curl -v https://d2uldy09xinkw1.cloudfront.net/6657752b-bdb6-44e5-96c2-b35bb223f633 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: R7OZj3J5zXhZW0q0Sottaw==' --http2

此外,多帶一個 connection: close 來搞破壞,也會造成 handshake 異常。

curl -v http://d2uldy09xinkw1.cloudfront.net/6657752b-bdb6-44e5-96c2-b35bb223f633 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: R7OZj3J5zXhZW0q0Sottaw==' --http1.1 -H 'Connection: close'

比較奇怪的,我沒有看到 connection 被改成 keep-alive 的狀況發生。WebSocket Upgrade 失敗目前看到的都是 connection: close 。後來,我發現這是 webhook.site 的問題,我用自己架設的 node.js server ,明明看到的都是 connection: keep-alive ,跟網頁上看到的不同。而且根據 CloudFront 文件實際上也應該要是 keep-alive 。

CloudFront replaces this header with Connection: Keep-Alive before forwarding the request to your origin.

CloudFront

實際收到的 header 如下,明顯可見 connection: keep-alive

{
  host: 'ec2-3-252-123-121.eu-west-1.compute.amazonaws.com',
  'user-agent': 'Amazon CloudFront',
  'x-amz-cf-id': '_j1gm97BZthEhhaCzEyzNWwYM_fYi4-29ExJ2pWyjmtdBS4uSdOlcw==',
  connection: 'Keep-Alive',
  via: '1.1 17c7dca456d18c7a1217f1dd39cdf4ec.cloudfront.net (CloudFront)',
  'x-forwarded-for': '54.240.197.234'
}


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *