做者簡介:
花名「卡庫」,白山雲科技系統開發工程師
API開發與管理老鮮肉,豐富的產品開發與運維經驗,前後就任於搜狐、新浪等知名互聯網公司,曾參與新浪雲SAE平臺CC防火牆項目,爲數十萬用戶提供安全防禦,保證SAE平臺性能穩定;2016年入職白山,就此成爲酒仙橋地區最大酒窩的系統開發工程師。html
針對實時Web應用(如:實時通訊、股票基金應用、體育實況更新、多玩家遊戲等場景),傳統Web中爲了實時獲取Server端的數據,一般是Client端按期發送HTTP請求,Server端進行響應並返回數據。因爲Client按期向Server發送請求,當Server端沒有數據更新時,Client仍舊發送請求,這形成了帶寬的浪費以及Server端CPU的佔用。python
爲解決上述問題,愈來愈多企業在思考如何解決長鏈接問題,WebSocket是較爲經常使用的方法之一。WebSocket經過第一個 HTTP request 創建 TCP 鏈接,後續數據交換都無需再發送 HTTP request,建立了一個真正的長鏈接。同時WebSocket 仍是一個雙通道的鏈接,能夠實如今同一個 TCP 鏈接上收發信息。後端
白山雲聚合平臺也融入了WebSocket,能夠爲用戶提供WebSocket協議到HTTP協議的轉換功能,讓用戶的Client以長鏈接WebSocket協議的方式鏈接到雲聚合平臺,雲聚合只需一個HTTP鏈接便可鏈接到企業後端,大幅下降後端壓力的同時,更免去了用戶服務器端適配WebSocket協議的問題。咱們在研發測試過程當中遇到了一個有意思的問題,這或許是不少開發者都曾遇到過的:使用不一樣的WebSocket客戶端和WebSocket Server通訊,WebSocket Server返回數據不一致。
1、問題場景
1.不一樣客戶端訪問
(1)python經過WebSocket客戶端和WebSocket Server ws://2abe356fc.bsclink.com/交互,輸出正常;
(python 客戶端輸出內容)
(2)Chrome瀏覽器加載ws.html頁面以後,頁面中的js調用瀏覽器自帶的WebSocket Client與WebSocket Server ws://2abe356fc.bsclink.com/交互,輸出ERROR;
(Chrome瀏覽器輸出內容)
(3)Safari瀏覽器加載ws.html頁面以後,頁面中的js調用瀏覽器自帶的WebSocket Client和WebSocket Server ws://2abe356fc.bsclink.com/交互,輸出正常;
(Safari瀏覽器輸出內容)
2.瀏覽器請求流程圖
如下是瀏覽器經過WebSocket協議向服務器請求的流程:
(瀏覽器請求流程圖)瀏覽器
2、問題分析安全
只有Chrome與Websocket Server間的通訊發生異常,判斷ERROR極可能是由Chrome瀏覽器問題致使的,基於此來分析問題產生的具體緣由。
經過瀏覽器控制檯查看報錯相關信息
服務器
如上圖下方所示,WebSocket協議decode a text frame在轉化爲uft-8編碼時失敗。
因爲WebSocket Server向Client返回數據時,使用text frame方式,因而咱們開始排查WebSocket Server返回數據致使decode失敗的緣由。網絡
打印WebSocket Server日誌,查看返回內容
經過日誌,觀察到longloop傳送給WebSocket Server的內容與WebSocket Server輸出到Client的內容一致,均爲亂碼。基於此咱們能夠肯定WebSocket Server不存在異常狀況,因而咱們須要肯定longloop是否存在異常。運維
經過longloop抓包查看backend返回內容
能夠經過TCPDUMP抓包來斷定longloop是否存在問題。socket
(backend返回到longloop的數據)
(longloop返回到WebSocket Server的數據)
經過對比以上兩組數據,能夠得出以下結論:
通過longloop後,真實返回給Client的數據並未發生變化。
(1) backend的返回數據被gzip壓縮;
(2) 壓縮的響應數據被髮送至WebSocket Server;
(3) 最終由WebSocket Server發送到WebSocket客戶端。oop
backend返回的數據爲何被壓縮了?
首先,backend端必須開啓gzip壓縮,並支持對此返回的數據類型的gzip壓縮,才能返回壓縮後的響應數據;
其次,客戶端要明確聲明能接收gzip壓縮的響應數據,backend端纔可以返回gzip壓縮過的數據。
經確認,backend server上的配置開啓了gzip壓縮功能,並對content-type爲text/html的數據支持gzip壓縮。
能夠判斷問題有可能出如今client環節:
Client沒有要求返回壓縮數據,可是backend端返回了壓縮數據;
經過不一樣瀏覽器訪問,返回不一樣數據,能夠斷定不是backend端的問題。
Client主動要求backend端返回被壓縮的數據;
只有Chrome瀏覽器返回了gzip壓縮數據,能夠推斷多是由於Chrome請求backend端時,在request header中包含了能夠接收gzip壓縮數據的header,致使backend端返回了gzip壓縮數據。
抓包對比Chrome和Safari請求頭信息
Chrome相關信息:
1) Chrome瀏覽器請求ws.html靜態文件的請求頭中帶有Accept-Encoding:
2)Chrome瀏覽器將ws.html加載到本地後,ws.html文件中的js WebSocket 客戶端向WebSocket Server發送請求的請求頭中帶有Accept-Encoding:
3)Chrome瀏覽器的請求發送到longloop以後,longloop到backend的請求頭中帶有Accept-Encoding:
Safari相關信息
1)Safari瀏覽器請求ws.html靜態文件的請求頭中帶有Accept-Encoding:
2)Safari瀏覽器將ws.html加載到本地後,ws.html文件中的js WebSocket 客戶端向WebSocket Server發送請求的請求頭中未帶有Accept-Encoding:
3)Safari瀏覽器的請求發送到longloop以後,longloop到backend的請求頭中未帶有Accept-Encoding:
經過對比Chrome和Safari相關請求數據,咱們能夠判斷出WebSocket Server返回數據不一致的緣由以下:
Chrome,Safari瀏覽器發送請求時,爲了提升網絡傳輸效率、減小網絡帶寬佔用,默認自帶gzip壓縮支持,兩種瀏覽器加載ws.html時均無異常。但當js調用Chrome瀏覽器WebSocket 客戶端向WebSocket Server端發送請求時,在請求頭Accept-Encoding中添加了對gzip的支持,backend收到HTTP請求後,認爲客戶端可以對gzip壓縮的響應數據進行解壓縮,從而backend返回了gzip壓縮過的響應數據,而WebSocket客戶端接收到gzip壓縮的數據後,不支持gzip數據解壓縮,最終致使了decode出錯。
而js調用Safari瀏覽器WebSocket客戶端向WebSocket Server端發送請求時,請求頭未帶有Accept-Encoding,backend收到http請求後,不會返回被gzip壓縮的響應數據,從而WebSocket客戶端正常解析訪問正常。
3、解決辦法爲解決上述問題,咱們須要在longloop這一層進行判斷:若是user agent爲Chrome瀏覽器,則須要去掉request header中的Accept-Encoding這個header,明確告知服務器端不接受gzip壓縮過的數據,這樣服務器端就不會返回gzip壓縮過的數據,Chrome瀏覽器便可正常訪問。