做者简介:
花名“卡库”,白山云科技系统开发工程师
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浏览器便可正常访问。