V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Baishancloud
V2EX  ›  推广

如何解决 WebSocket Server 返回数据不一致

  •  
  •   Baishancloud · 2017-02-27 19:10:28 +08:00 · 2988 次点击
    这是一个创建于 2845 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作者简介:

    花名“卡库”,白山云科技系统开发工程师

    API 开发与管理老鲜肉,丰富的产品开发与运维经验,先后就职于搜狐、新浪等知名互联网公司,曾参与新浪云 SAE 平台 CC 防火墙项目,为数十万用户提供安全防护,保证 SAE 平台性能稳定; 2016 年入职白山,就此成为酒仙桥地区最大酒窝的系统开发工程师。


    针对实时 Web 应用(如:实时通信、股票基金应用、体育实况更新、多玩家游戏等场景),传统 Web 中为了实时获取 Server 端的数据,通常是 Client 端定期发送 HTTP 请求, Server 端进行响应并返回数据。由于 Client 定期向 Server 发送请求,当 Server 端没有数据更新时, Client 仍旧发送请求,这造成了带宽的浪费以及 Server 端 CPU 的占用。

    为解决上述问题,越来越多企业在思考如何解决长连接问题, WebSocket 是较为常用的方法之一。 WebSocket 通过第一个 HTTP request 建立 TCP 连接,后续数据交换都无需再发送 HTTP request ,创建了一个真正的长连接。同时 WebSocket 还是一个双通道的连接,可以实现在同一个 TCP 连接上收发信息。

    白山云聚合平台也融入了 WebSocket ,可以为用户提供 WebSocket 协议到 HTTP 协议的转换功能,让用户的 Client 以长连接 WebSocket 协议的方式连接到云聚合平台,云聚合只需一个 HTTP 连接即可连接到企业后端,大幅降低后端压力的同时,更免去了用户服务器端适配 WebSocket 协议的问题。我们在研发测试过程中遇到了一个有意思的问题,这或许是很多开发者都曾遇到过的:使用不同的 WebSocket 客户端和 WebSocket Server 通信, WebSocket Server 返回数据不一致。

    一、问题场景

    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 协议向服务器请求的流程:

    (浏览器请求流程图)


    二、问题分析

    只有 Chrome 与 Websocket Server 间的通信发生异常,判断 ERROR 很可能是由 Chrome 浏览器问题导致的,基于此来分析问题产生的具体原因。


    1.通过浏览器控制台查看报错相关信息

    如上图下方所示, WebSocket 协议 decode a text frame 在转化为 uft-8 编码时失败。

    由于 WebSocket Server 向 Client 返回数据时,使用 text frame 方式,于是我们开始排查 WebSocket Server 返回数据导致 decode 失败的原因。


    2.打印 WebSocket Server 日志,查看返回内容

    通过日志,观察到 longloop 传送给 WebSocket Server 的内容与 WebSocket Server 输出到 Client 的内容一致,均为乱码。基于此我们可以确定 WebSocket Server 不存在异常情况,于是我们需要确定 longloop 是否存在异常。


    3.通过 longloop 抓包查看 backend 返回内容

    可以通过 TCPDUMP 抓包来判定 longloop 是否存在问题。

    ( backend 返回到 longloop 的数据)

    ( longloop 返回到 WebSocket Server 的数据)

    通过对比以上两组数据,可以得出如下结论:

    经过 longloop 后,真实返回给 Client 的数据并未发生变化。

    ( 1 ) backend 的返回数据被 gzip 压缩;

    ( 2 ) 压缩的响应数据被发送至 WebSocket Server ;

    ( 3 ) 最终由 WebSocket Server 发送到 WebSocket 客户端。


    4.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 压缩数据。


    5.抓包对比 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 客户端正常解析访问正常。


    三、解决办法

    为解决上述问题,我们需要在 longloop 这一层进行判断:如果 user agent 为 Chrome 浏览器,则需要去掉 request header 中的 Accept-Encoding 这个 header ,明确告知服务器端不接受 gzip 压缩过的数据,这样服务器端就不会返回 gzip 压缩过的数据, Chrome 浏览器即可正常访问。

    3 条回复    2017-03-02 18:56:49 +08:00
    ryd994
        1
    ryd994  
       2017-02-27 21:31:20 +08:00 via Android
    这样的 workaround 不好
    是 chrome 违反了 accept-encoding 声明了自己不支持的
    还是 Nginx 对不该 gzip 的数据 gzip 了
    无论是哪个,应该直接向上游提交 issue ,再 workaround ,在 issue 解决后去掉

    关于 gzip websocket ,似乎暂时没有标准: http://stackoverflow.com/questions/11646680/could-websocket-support-gzip-compression
    jingniao
        2
    jingniao  
       2017-02-27 21:50:12 +08:00
    没遇到这个问题,倒是在选型的时候遇到过某些库的动作有些奇怪
    Baishancloud
        3
    Baishancloud  
    OP
       2017-03-02 18:56:49 +08:00
    @ryd994
    恩,您说的很对。之前定解决办法的时候也想到了,源站的 nginx 由于是企业客户那边的,不好控制,故没有采用。 Chrome 的问题后续也会跟踪。考虑到客户测试,故直接 workaround 了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   903 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 20:58 · PVG 04:58 · LAX 12:58 · JFK 15:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.