工作時使用了Websocket技術,在使用的過程中發現,瀏覽器(Chrome)升級後可能會導致Websocket不可用,更換瀏覽器後可以正常使用。
近日偶爾一次在本地除錯,發現使用相同版本的Chrome瀏覽器,不可連線線上伺服器的WS服務,但是可以連線本地的WS服務。 此時初步懷疑是伺服器在某種特殊情況下會觸發無法連線的問題。
使用Wireshark抓包
Filter: ip.dst==serverIP or (ip.dst==本地IP and ip.src==serverIP)
一.檢視可以正常連線線上服務的瀏覽器的網路請求(搜狗高速核)
可以看到WebSocket連線建立的步驟:
1、先建立TCP連線,1~3條為tcp連線的三次握手
2、發出一個http請求,Header內容如下
GET /write?agentId=255 HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 10.134.71.235:2015
Origin: http://10.134.71.235
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: NddL4PEqgeUKIon0p+IHwQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0
Cookie: ASP.NET_SessionId=kdparak1ecjplo4erozul2yl; _un=zouchengzhuo@sogou-inc.com; id=77.NRe6bXSRddXY6INl1HMkRAdn7L4yIt4wcTGYu43q9r4; un=zouchengzhuo@sogou-inc.com; pw=70467311a7ed8f62b58f8f1d65cdb408
伺服器返回
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: LDedYtTLpS6J7EygF4awEchi+D4=
3.連線建立成功,使用WebSocket協議收發訊息
其中[FIN][MASKED] 為瀏覽器給server發訊息
[FIN]為server給瀏覽器發訊息,瀏覽器收到後發一個TCP的 [ACK] 包確認
正常的網路請求知道了,接下來伺服器不變,更換瀏覽器
二、檢視無法正常連線線上伺服器的網路請求(Chrome)
可以看到,HTTP請求發出去後,沒有收到101的回覆,伺服器直接發起了TCP連線斷開的流程。
懷疑是HTTP請求的內容不對。檢視請求header
GET /write?agentId=255 HTTP/1.1
Host: 10.134.71.235:2015
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://10.134.71.235
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: ASP.NET_SessionId=1rfwnghibfq2jvvjrlbflvl0; _un=zouchengzhuo@sogou-inc.com; id=77.NRe6bXSRddXY6INl1HMkRAdn7L4yIt4wcTGYu43q9r4; un=zouchengzhuo@sogou-inc.com; pw=IsQky+6I5U5zM89pna9UNNirBD9v74G5799FdJvrK78aLTw0mvq5icQJpNlCweeHTl646j88InE03ayWm4PpcA==
Sec-WebSocket-Key: kcEwLRS2BowYzsoYxGCQNw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
和搜狗的包對比,好像沒有任何問題,接下來瀏覽器不變,更換伺服器
三、檢視可以正常連線的本地伺服器的網路請求(Chrome)
連線建立過程正常,就不用截圖了,主要關注HTTP請求header裡邊的內容
GET /write?agentId=255 HTTP/1.1
Host: 10.129.157.168:2015
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin:http://localhost:8317
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Sec-WebSocket-Key: ldwAY7BvJ6c0Gt9Xbh/R/Q==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
分析原因
分析發現,這個header裡邊沒有cookie的資料,推測有可能是http請求裡邊的cookie導致連線斷開。
chrome
這裡就發現了第一個奇怪的問題,Chrome為什麼有的時候會傳送cookie,有的時候不會呢?
經過多次除錯發現:
1.關閉瀏覽器並重啟的第一次,使用m.venus.sogou-inc.com訪問,是可以正常使用的
2.無論何時,通過IP直接訪問伺服器都無法連線
3.只要用IP訪問過,用域名訪問也無法正常連線了
分析Cookie
用域名訪問時的Cookie
用IP訪問時的Cookie
公司對m.venus.sogou-inc.com的解析是經過了代理伺服器的,目前OP的nginx還不能轉發ws協議的請求
所以瀏覽器建立Websocket的方式 是 new WebSocket('ws://ip:port') 而不是 new WebSocket('ws://hostname:port')
重啟瀏覽器,用域名訪問的時候,header中
Host: 10.134.71.235:2015
Origin: http://m.venus.sogou-inc.com
這是一個跨域的http請求,所以請求中預設是不會帶上cookie的,chrome對在ws協議中的http請求中,顯然也是應用的這個預設設定。
用IP訪問的時候
Host: 10.134.71.235:2015
Origin: http://10.134.71.235
這是一個同域的請求,所以會帶上cookie。 第二個問題得到了解釋
一旦用ip訪問過,在此ip下就種下cookie了,而即使是在域名訪問的情況下,ws協議發出的http請求的host也是ip:port,所以也會傳送ip下的cookie。
可以看到cookies的生命週期都是session級別的,所以重啟瀏覽器後再用域名訪問是可以的
第一、三個問題得到了解釋
但是,搜狗瀏覽器的請求裡邊也包含了Cookie,為何搜狗卻可以正常連線伺服器呢?
搜狗高速核
用搜狗高速核多次試驗,發現:
1.搜狗高速核的cookie資料中沒有傳送sessionid
2.搜狗瀏覽器不管任何時候,都會傳送cookie
用域名訪問的cookie:
用IP訪問的cookie:
通過對比cookie的值發現,傳送的cookie是ip域下的cookie。
那麼這裡有兩個沒辦法解釋的問題
1.為什麼帶有httponly屬性的cookie,搜狗瀏覽器在傳送請求的時候不會發出去
2.為何關掉所有搜狗瀏覽器的程式後,生命週期為session的cookie沒有被幹掉
這是不是搜狗瀏覽器的兩個BUG呢?
到此時基本可以確定,就是因為cookie中帶了sessionid導致伺服器主動斷開連線。
雲平臺的Websocket伺服器是用Alchemy Websockets開發的。按照正常的邏輯,WebSocket伺服器中不應該用session去判斷使用者身份,因為它和Http不屬於同一個會話。
現在懷疑是因為Alchemy WebSockets元件的BUG導致此問題。
除錯Alchemy WebSockets原始碼
首先在本地IIS中釋出一個雲平臺伺服器,然後在VS中開啟一個除錯的服務,通過IP登入一下本地的雲平臺,然後通過localhost:除錯埠 訪問除錯的服務,以模擬ws連線的http請求中帶上cookie的情況。
從連線開始階段打上斷點單步除錯,到Handshake.cs類的時候發現一個方法:
public bool IsValid() { return ( (Host != null) && (Key != null) && (Int32.Parse(Version) >= 8) ); }
在正常情況下此方法返回true,請求中帶上cookie且含有sessionId後返回false。原因是key==null。
抓包得到的header:
GET /write?agentId=261 HTTP/1.1
Host: 10.129.157.168:2015
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:8317
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: ASP.NET_SessionId=5qmgzihkxtvntxirmekbm2tv; _un=zouchengzhuo@sogou-inc.com; id=77.NRe6bXSRddXY6INl1HMkRAdn7L4yIt4wcTGYu43q9r4; un=zouchengzhuo@sogou-inc.com; pw=RGPCwqEawjg9JXHVZ/rLzM4Ac1+nDHlL2y1kKYp6PVLkZ5o/Oj5/OsP8t8vsg3D+djE3x0Q7zH/7ggw2Jme63A==
Sec-WebSocket-Key: TLjHvbrhmKqEK3sNPu7bnA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_windo
可以看到Sec-Websocket-Key 是存在的。
除錯進入分析處理header的類裡邊,發現收到的header字串為
GET /write?agentId=261 HTTP/1.1
Host: 10.129.157.168:2015
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:8317
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: ASP.NET_SessionId=5qmgzihkxtvntxirmekbm2tv; _un=zouchengzhuo@sogou-inc.com; id=77.NRe6bXSRddXY6INl1HMkRAdn7L4yIt4wcTGYu43q9r4; un=
發現header少了一段。繼續分析這個元件的程式碼,發現
Handler.cs 中:
TCPServer中:
預設只讀取了512位元組的資料,把這裡改為一個足夠大的大小來測試一下,就沒問題了。
至此問題的原因找到了,Alchemy WebSockets在處理ws連線第二步——傳送http請求的時候,對http頭的解析方法有問題,導致丟掉了關鍵性的資料,瀏覽器中生成的 Ses-Websocket-Key,以至於伺服器認為這是個非法的連線請求,給幹掉了。
將處理header的地方修改為讀取所有資料再處理,就能解決這個問題。
但是除錯過程中還是留下了兩個疑問:
1.為什麼帶有httponly屬性的cookie,搜狗瀏覽器在傳送請求的時候不會發出去
2.為何關掉所有搜狗瀏覽器的程式後,生命週期為session的cookie沒有被幹掉