WebSocket 測試
從瀏覽器開一條 WebSocket,送出訊框並看伺服器回什麼。直接連到你的伺服器,所以 localhost 也連得上;1006 會解釋而不是只印出來。
在你的瀏覽器中執行
收發紀錄
- 目前沒有紀錄。
你的瀏覽器直接連到你輸入的位址,不會經過本站——這也是為什麼本機位址也連得上。
關閉代碼
| 代碼 | 意義 |
|---|---|
| 1000 | Normal closure |
| 1001 | Going away |
| 1002 | Protocol error |
| 1003 | Unsupported data |
| 1005 | No status received伺服器不會送出 |
| 1006 | Abnormal closure伺服器不會送出 |
| 1007 | Invalid frame payload data |
| 1008 | Policy violation |
| 1009 | Message too big |
| 1010 | Mandatory extension missing |
| 1011 | Internal server error |
| 1012 | Service restart |
| 1013 | Try again later |
| 1014 | Bad gateway |
| 1015 | TLS handshake failed伺服器不會送出 |
連線是你的瀏覽器發起的,不是本站
這條連線由你自己的瀏覽器直接連到你輸入的位址,完全不經過本站。這件事有兩個值得知道的後果:
ws://localhost:8080連得上。 跑在你這台機器、或你公司內網上的伺服器,在這裡連得到,即使 網際網路上沒有任何伺服器連得到它。那些把連線繞過自家後端的測試站做不到這件事。- 你送出去的東西,只有你的伺服器看得到。 路徑上沒有第三方,因為設計上就沒有第三方。
關閉代碼 1006——大多數人是為了它來的
1006 不是你的伺服器說的話。你的伺服器送不出這個碼,規格明文禁止,1005 和 1015 也一樣。
它是瀏覽器在「連線結束了,但沒有收到關閉訊框」時所報的,意思僅止於此:連線死了,而沒有人說原因。
下面這串裡的某一個,而 WebSocket API 不會告訴你是哪一個:
- 那個主機和連接埠上根本沒有東西在聽。
- TLS 失敗——
wss://那端的憑證過期、自簽,或網域對不上。 - 你和伺服器之間的代理伺服器或負載平衡器沒有把 upgrade 轉過去。
- 伺服器行程掛了,或連到一半網路斷了。
- 伺服器用一個 HTTP 狀態碼、而不是
101,回絕了這次握手。 - 你提出了子協定,而伺服器沒有回一個給你——見下方。
API 是刻意不給原因的。 一個網頁不可以有能力分辨「連線被拒」和「路由不通」,因為這個差別足以 讓網頁掃描訪客的內網。不是瀏覽器知道卻不給你,是瀏覽器刻意不往上傳。
真正的錯誤通常在瀏覽器自己的主控台裡,而那個地方 JavaScript 碰不到。打開開發者工具,看 主控台和網路分頁的 WS 標籤,常常就能看到那次握手實際拿到的 HTTP 狀態。
混合內容:在任何流量之前就失敗
這個頁面是以 HTTPS 提供的,而安全頁面不允許開啟純 ws:// 連線。瀏覽器會在一個位元組送出去之前
就擋下來——然後把它報成一般的連線失敗,看起來跟伺服器掛掉一模一樣。
這個頁面在開啟連線之前就先檢查,並直接把原因說出來。
localhost 是例外:瀏覽器把回送位址視為安全環境,所以從 HTTPS 頁面用 ws:// 測本機開發伺服器
是可以的。其他位址一律要 wss://。
握手,以及代理伺服器為什麼會弄壞它
WebSocket 一開始是一個普通的 HTTP GET,帶著 Upgrade: websocket 和 Connection: Upgrade,只有
在伺服器回 101 Switching Protocols 時才算成功。回任何其他狀態碼都到此為止。
幾乎每一個「本機好好的、上線就爛掉」的 WebSocket 問題,都是反向代理把這兩個標頭吃掉了。nginx 需要明確把它們加回去:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
proxy_http_version 1.1 這行是關鍵——預設是 1.0,而 1.0 根本沒有 upgrade 機制。逾時那行也是:
nginx 預設 60 秒就會關掉閒置連線,症狀是「連線一切正常,然後剛好一分鐘後死掉」。
子協定
第二個選填欄位會送出 Sec-WebSocket-Protocol 標頭,按偏好順序提出一份清單。伺服器挑一個回
給你,頁面上會顯示挑中的是哪一個。實務上會遇到的是 graphql-ws、mqtt、json。
提出一個伺服器沒有回應的子協定,連線會直接死掉,而這一點很常讓人卡住。伺服器一個都不認得
時,它會照樣完成握手、但不回這個標頭——而 RFC 6455 接著要求用戶端必須讓這條連線失敗。瀏覽器
就是這麼做的,於是你拿到 1006,跟「伺服器根本沒開」拿到的是同一個碼。
所以填了子協定就秒斷、清空就連得上,那不是伺服器壞了,是那台伺服器不講你指定的那個子協定。
你不能設標頭,而這件事決定了驗證怎麼做
瀏覽器的 WebSocket API 只收一個網址和一份子協定清單,就這樣。沒有辦法送出 Authorization
標頭——這裡不行,任何瀏覽器裡都不行,換哪個函式庫都不行。
所以伺服器端的驗證只能走別的路:
- Cookie,會自動跟著該來源的連線送出。最簡單,但要注意 WebSocket 握手不受 CORS 保護, CSRF 那一類的顧慮還在。
- 放在查詢字串裡的 token,
wss://host/ws?token=…。用的人很多,代價是它會進到存取紀錄和 代理伺服器的紀錄裡。請用短效期的 token。 - 連線建立後的第一則訊息,伺服器在收到之前什麼都不做。最麻煩,也最乾淨。
有些函式庫看起來能從瀏覽器送 Authorization 標頭。它們沒有,它們是把它塞進第一個訊框裡。
關閉代碼
上面的表格列出了全部。要記的是這個結構:
- 1000–1015 由協定定義。
1000是正常關閉、1001是頁面或伺服器要離開、1009是訊框超過 大小上限、1011是伺服器內部錯誤。 - 3000–3999 是函式庫與框架向 IANA 註冊的。
- 4000–4999 是你的。想讓某個關閉代表你自己應用程式裡的某件事,就用這一段——不會有別的東西 來撞。
瀏覽器只被允許送出 1000 或 3000–4999 之間的代碼。從這個頁面按中斷,送出的是 1000。
相關
我的 IP 位址是你的伺服器看到的東西,
CSR 產生器處理 wss:// 背後那張憑證,
訊框收回來之後可以用 JSON 格式化讀。