使用retrofit时,gzip带来的坑

来源:互联网 发布:linux 设置文件夹权限 编辑:程序博客网 时间:2024/06/10 00:21

昨天下午,突然线上用户反馈,不能正常登陆,吓尿了。本人和后台距离上次发包已经1个多星期过去了,且测试正常BUG反馈全部结束完成的情况下发的包,突然出现问题。

马上开始解决问题,DEBUG开到生产环境下,两段都没有发现任何的错误。由于对产品的安全性做了相应的措施,产品的每个端口都有签名要效验。就在这个时候发现,乱码了。

开始解决问题

能发生乱码,必然就是原始字节流发生了什么问题,但是从后台看到的日志,响应的文字都是正常的UTF-8可解读的,且请求头的charset=UTF-8,第一次挠头,后台没问题。
这时前端的请求日志为

HTTP/1.1  200  Date:  Mon,  04  Dec  2017  10:34:24  GMTContent-Type:  application/json;charset=UTF-8Vary:  Accept-EncodingSet-Cookie:  yd_cookie=4f620b07-a120-49968ba3d253d11d1b785f3bf8e4f2ed5db0;  Expires=1512390864;  Path=/;  HttpOnlyX-Application-Context:  im-gateway:prod:8000sdkSign:  xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxServer:  WAF/2.4-12.1Content-Encoding:  gzipTransfer-Encoding:  chunkedProxy-Connection:  Keep-alive

其实,大佬可能已经看到了端倪。我们待会再说。经过一个小时的排查,决定尝试用裸IP跑一下,结果突然发现是response返回正常字节,在逻辑不动的情况下,可正常接收解析。
此时请求日志为:

Server: nginx/1.12.0Date: Mon, 04 Dec 2017 11:37:07 GMTContent-Type: application/json;charset=UTF-8Transfer-Encoding: chunkedConnection: keep-aliveX-Application-Context: im-gateway:prod:8000sdkSign:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

没错,此时本文的主角就出现了,Content-Encoding: gzip,在上下两个请求文本的对比下看到了。

HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的。

原来,运营下午在客户的需求下,打开了高防,且为了加速设置了gzip缓存。

举个简单的例子,我们知道数据的最原始形态都是二进制的,计算机看到的就是0100010100110000000001这样的东西,那压缩就是把之间的0变成了8/0类似这样的,减少了实际的字节长度,从来达到压缩的目的。那很明显,看到了gzip,那就要给response做解压。

重要代码

if (GZIPUtils.isGzip(response.headers())) {    //请求头显示有gzip,需要解压    data = GZIPUtils.uncompress(data);}

本人近期网络请求都是习惯于用retrofit2,所以解释一下这个问题。强大的网络请求框架能被很多人拥护,自然这样的问题肯定会很好的规避。又因为解析工厂在交给Gosn之后,选择了在网络拦截器Interceptor上过滤响应,辨别是否为服务器正规响应。

public class NetworkInterceptor implements Interceptor {        @Override        public Response intercept(Chain chain) throws IOException {            Request request = chain.request();            Response response = chain.proceed(request);            boolean checked = true;            if (response.code() == 200) {                //这里是网络拦截器,可以做错误处理                MediaType mediaType = response.body().contentType();                //当调用此方法java.lang.IllegalStateException: closed,原因为OkHttp请求回调中response.body().string()只能有效调用一次                //String content = response.body().string();                byte[] data = response.body().bytes();                if (GZIPUtils.isGzip(response.headers())) {                    //请求头显示有gzip,需要解压                    data = GZIPUtils.uncompress(data);                }                //获取签名                String sdkSign = response.header("sdkSign");                try {                    //效验签名                    checked = RSAUtils.verify(data, GlobalField.APP_SERVICE_KEY(), sdkSign);                } catch (Exception e) {                    e.printStackTrace();                }                if (!checked) {                    return null;                } else {                    //创建一个新的responseBody,返回进行处理                    return response.newBuilder()                            .body(ResponseBody.create(mediaType, data))                            .build();                }            } else {                return response;            }        }    }

这就是一切罪恶的根源,如果不处理,retrofit2是默认可以接受带有gzip的字符集的。且在这里有一个注意的点,response.body().string()这个方法一旦使用,只允许使用一次,response中body的content会直接为空,需要在这里做处理的同学主意这个问题

附上一工具类,便于解决解压和压缩用

import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.zip.GZIPInputStream;import java.util.zip.GZIPOutputStream;import okhttp3.Headers;/** * Created by gaozhen on 2017/12/4. */public class GZIPUtils {    public static final String ENCODE_UTF_8 = "UTF-8";    public static final String ENCODE_ISO_8859_1 = "ISO-8859-1";    /**     * String 压缩至gzip 字节数据     */    public static byte[] compress(String str) {        return compress(str, ENCODE_UTF_8);    }    /**     * String 压缩至gzip 字节数组,可选择encoding配置     */    public static byte[] compress(String str, String encoding) {        if (str == null || str.length() == 0) {            return null;        }        ByteArrayOutputStream out = new ByteArrayOutputStream();        GZIPOutputStream gzipInputStream;        try {            gzipInputStream = new GZIPOutputStream(out);            gzipInputStream.write(str.getBytes(encoding));            gzipInputStream.close();        } catch (IOException e) {            System.out.println("gzip compress error");        }        return out.toByteArray();    }    /**     * 字节数组解压     */    public static byte[] uncompress(byte[] bytes) {        if (bytes == null || bytes.length == 0) {            return null;        }        ByteArrayOutputStream out = new ByteArrayOutputStream();        ByteArrayInputStream in = new ByteArrayInputStream(bytes);        try {            GZIPInputStream gzipInputStream = new GZIPInputStream(in);            byte[] buffer = new byte[256];            int n;            while ((n = gzipInputStream.read(buffer)) >= 0) {                out.write(buffer, 0, n);            }        } catch (IOException e) {            System.out.println("gzip uncompress error.");        }        return out.toByteArray();    }    /**     * 字节数组解压至string     */    public static String uncompressToString(byte[] bytes) {        return uncompressToString(bytes, ENCODE_UTF_8);    }    /**     * 字节数组解压至string,可选择encoding配置     */    public static String uncompressToString(byte[] bytes, String encoding) {        if (bytes == null || bytes.length == 0) {            return null;        }        ByteArrayOutputStream out = new ByteArrayOutputStream();        ByteArrayInputStream in = new ByteArrayInputStream(bytes);        try {            GZIPInputStream ungzip = new GZIPInputStream(in);            byte[] buffer = new byte[256];            int n;            while ((n = ungzip.read(buffer)) >= 0) {                out.write(buffer, 0, n);            }            return out.toString(encoding);        } catch (IOException e) {            System.out.println("gzip uncompress to string error");        }        return null;    }    /**     * 判断请求头是否存在gzip     */    public static boolean isGzip(Headers headers) {        boolean gzip = false;        for (String key : headers.names()) {            if (key.equalsIgnoreCase("Accept-Encoding") && headers.get(key).contains("gzip") || key.equalsIgnoreCase("Content-Encoding") && headers.get(key).contains("gzip")) {                gzip = true;                break;            }        }        return gzip;    }}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 微信解冻短信验证总显示失败怎么办 淘金币能抵钱商家拿了淘金币怎么办 真实订单被系统判定虚假交易怎么办 淘宝买家号疑似虚假交易违规怎么办 货品交易一方收了定金违约了怎么办 饿了么店铺收到差评怎么办 淘宝顾客退款没成功给差评怎么办 身份证绑定了淘宝注册支付宝怎么办 把钱转错到支付宝账号被扣了怎么办 网上买东西收到信息被删掉了怎么办 表格在手机上显示不出来怎么办? 电子表格中复制后没有虚线框怎么办 word中页眉页脚横线短了怎么办 亿图图示画的图不显示怎么办 掌柜宝用了几天无法登录了怎么办 手机千牛消息不小心删除了怎么办 淘宝账号被冻结提示无法恢复怎么办 商家未发货我误点了确认收货怎么办 淘宝买货商家不发货也不退款怎么办 商家没发货点成收货了怎么办 淘宝新店上架产品未通过审核怎么办 安卓手机电池虚电怎么办_查查吧 拼多多拼单期间商家下架商品怎么办 微信绑别人的卡需要人脸认证怎么办 支付宝把钱转到了停用的账号怎么办 我注册征信账号忘记了要怎么办 客户说考虑考虑我该怎么办招商类 浴巾用久了黑黑的洗不干净怎么办 wifi密码被别人改了怎么办啊 苹果手机更新后账号密码忘了怎么办 专项预审批额度额度时效了怎么办 id密码和id码丢了怎么办 手机卡丢了id密码忘了怎么办 不想叫爱派登录我的微信怎么办 pr中素材与新建序列不匹配怎么办 淘宝客户收到货不承认要退款怎么办 淘宝包邮店铺 新疆地区拍怎么办 唯品会换货上门但自己寄了怎么办 闲鱼实名认证拍身份证不行怎么办 支付宝绑定的身份证过期了怎么办 实名认证的淘宝店铺被骗走了怎么办