nginx+lua 记一次后端程序压缩数据后导致前端乱码问题

前言

大家都知道压缩包如果不解压直接查看的话,都是一堆乱码,目前我们使用nginx+lua 对用户请求进行代理时( 客户端-->ngx+lua-->后端服务器 ),发现有些请求返回的数据都是乱码,这就乱码的原因就是直接查看未解压的压缩包

问题表现

我们在上线候测试时,发现浏览器某个按钮不能用了,通过F12查看发现这个请求返回内容都是乱码,如下所示:

我又上日志上查看,发现nginx记录的日志也是乱码,如下所示:

排查过程

我第一感觉认为是后端服务器返回了乱码数据给我,所以才是乱码的,呵呵。于是找负责人去了,人家说没有动呀。于是我们只能从自己身上找问题。我想了以下可能会出问题的点:

  1. 后端数据本身有问题(已被排除)
  2. 后端返回数据太大,nginx+lua只收了一些数据,数据没收全导致。
  3. 后端的数据被压缩了导致乱码

第一点毫无疑问排除后,就准备验证第二点,我通过tcpdump 在服务端(后端服务器)和nginx+lua上抓包,抓包能够分析的更全面所以优先考虑它,然后下载PC机用wireshark 分析,由于是HTTPS传输的,都是加密的内容,wireshark 啥也看不到,如下所示:

我参考网上很多文章尝试去解开这个HTTPS数据流,结果都搞不定,花了半天时间,就放弃了。那怎么去验证?换条路,nginx+lua里面使用的 lua-resty-http(github地址) 模块去构造HTTP请求,那可以很方便查看接受到的数据长度,在日志里面打印出接受的数据长度即可,然后查看后端服务器的nginx日志,看看nginx发送的数据长度有多少。

lua记录接收到的数据长度:

            res,err = httpc:request_uri(url, {
                method = method,
                body = body,
                headers = headers,
                keepalive_timeout = 60000,  -- ms
                keepalive_pool = 40,
            })

            local body = res.body
            log(ERR,"body length: ",#body)

我通过对比发现,nginx+lua收到的数据竟然是510字节,而后端服务器的nginx日志显示发送了1300字节,这个怎么回事呢? 难道真的是因为数据没有收全导致乱码? 带着这些问题,我继续深挖。

我首先翻开lua-resty-http的源代码,基本上把request_uri这个方法的仔细看了一遍,然后在里面各个条件分支上打了日志,发现乱码的请求,都会走到这个条件分支:

        if version == 1.1 and str_find(encoding, "chunked", 1, true) ~= nil then
            body_reader, err = _chunked_body_reader(sock)

这大概找到了前进的方向了,chunked 这个关键字。然后继续往下走,在 _chunked_body_reader 发现一个有意思的:

length = tonumber(str, 16)

我一开始没有想明白这个怎么回事(原谅鄙人才疏学浅),经过查阅相关资料才发现,chunked传输都是16进制的,所以这个长度在这里转了下,需要转为16进制。

,于是继续往前走

我对比了不乱码的请求和乱码的请求,发现请求的头信息,接口地址都一样,唯一不同的就是GQL语句不一样,我又对比返回来的头信息,发现乱码的头信息多了2字段,他们是

  1. Transfer-Encoding : chunked
  2. Content-Encoding : gzip

第一个chunked表示数据太长需要进行分片(个人理解,应该没有理解歪曲),第二个gzip表示内容编码 格式为 gzip,其实还有点被我忽略了,前端请求时的头信息有这一段:Accept-Encoding: gzip ,这表明可以返回的数据接受gzip的编码。所以服务器端就进行压缩了。

了解了以上几点,我此时大概可以推断出来。 ngx+lua 接受的数据是510,数据不全的原因那么就有4种情况:

  1. 数据内容太长被切片传输了(这个应该被排除,因为Content-Encoding : gzip表明已被压缩了)。
  2. 数据内容被压缩了
  3. 数据内容太长,切片传输前被压缩了。
  4. 数据内容太长,压缩后再进行切片传输。

到此,只能是一一排除了。我最开始去验证第二个情况,我把接收到的乱码内容写到磁盘上,再拿到PC机的360解压,发现解压后的内容就是json串。
写入磁盘代码如下:

local f = io.open("/tmp/luanma.zip","w")
f:write(resp.body)
f:close()

处理方法

显然,我们一下子就碰对了。找到问题了,这个就是后端服务器返回数据的时候,由于数据稍微长点,所以就进行了压缩,导致nginx+lua 接收到的数据是二进制的,需要再解压下才能还原回json串。于是添加以下代码:

        local ret = res.body
        if res.headers["Transfer-Encoding"] == "chunked" and string.find( res.headers["Content-Encoding"],"gzip" ) ~= nil then
            ret = zlib.inflate()(ret)  -- 这里是压缩文件,需要进行解压处理,否则浏览器收到是乱码
        end
        ngx.print(ret)

至此,添加完这个代码,重启下nginx,问题被解决了。

zlib 的使用方法可以查看官网: https://github.com/brimworks/lua-zlib/

安装zlib

我这里使用apt安装,源码安装比较费劲,所以推荐使用apt-get或yum 。ubuntu系统如下

apt-get install lua-zlib lua-zlib-dev
cp /usr/lib/x86_64-linux-gnu/lua/5.1/zlib.so  /usr/local/lib/lua/5.1/ # 一定要复制过去,不然引入zlib会提示找不到。

杂谈

问题1

为什么会被压缩,我个人认为是 nodejs 发请求时明确了可以接受gzip压缩内容,http头信息里面 Accept-Encoding: gzip 表明了这点,所以后端返回了压缩内容,

问题2

奇怪的是,我看所有的请求头信息都有这个字段,但是只有个别请求的内容被压缩了。我个人认为是数据长度太长了。首次发现乱码的请求,返回的数据长度接近2K

posted @ 2021-01-30 15:19  温柔易淡  阅读(1145)  评论(1编辑  收藏  举报