文件下载时神秘消失的24字节

文件下载时神秘消失的24字节

代码

 public static HttpServletResponse downloadZip(File file, HttpServletResponse response) {
        OutputStream toClient = null;

        InputStream fis = null;
        try {
            // 以流的形式下载文件。
            fis = new BufferedInputStream(new FileInputStream(file.getPath()));
            // 清空response
            response.reset();
            toClient = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            //如果输出的是中文名的文件,在此处就要用URLEncoder.encode方法进行处理
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
            final StringBuffer stringBuffer = new StringBuffer();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1) {
                toClient.write(buffer, 0, len);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (toClient != null) {
                try {
                    toClient.flush();
                    toClient.close();
                    fis.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
        return response;
    }

现象

  1. 本地下载正常没有问题,部署到测试环境下载正常,但是zip文件下载后无法解压???

排查

​ 发现这个现象后我就开始了我疯狂的排查工作

  1. 文件解压不了?文件完整性如何?

    于是我就本地下载了一份文件,从测试服务器下载了一份文件,这时就出现异常了? 本地下载的文件是774B,服务器下载的文件是750B.???我消失了24B

  2. 下一步就是查代码,代码就是这几行,在哪里会丢字节呢?流,这是我唯一可以想到的了(但是我失策了)

  3. 于是我开始疯狂的查询资料,不同流的比较,性能,安全性,然后是各种实验,实验结果当然可想而之,毫无作用,永远是本地好事但是测试不行.

经过半天的疯狂实验后得出了一个结论:我的代码没有问题.卧槽,那问题在哪?本地找不出问题我就上测试找,

  1. 文件先不下载,直接在服务器生成,我上到服务器上检查unzip可以正常解压—文件生成没有问题

  2. 由于我们的服务是docker部署我就进到docker内直接请求下载接口curl,下载下来了!!!震惊!!!下载下来了!!好了吗?

  3. 我又从测试swagger下载,失败!!!一脸懵逼!!!

  4. 接着找,docker可以下载,那过了docker之后的数据流向呢?宿主机,我又在宿主机上下载文件 , 成功 !! — docker与宿主机的网络通信过程中没有丢失.

  5. 微服务网关,最后的排查,通过网关下载文件的时候失败!! ----- 问题出现在网关上

开始排查网关

  1. 框架使用的是zuul,网关代码不是太多,大多是配置,针对与请求的只有几个Filter,授权,统一错误处理都与我无关,只有一个PostFilter

  2. 这个类主要的作用是统计每个请求的时间,并记录请求日志,但是里面有几行特殊代码

    InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();
    String body = IOUtils.toString(stream);
    RequestContext.getCurrentContext().setResponseBody(body);
    
  3. 获取我的返回数据流,转换为String 放入body,看似没有什么问题,但是RequestContext.getCurrentContext().getResponseDataStream();

  4. 按照流本身所代表的抽象含义,数据一旦流过去,就无法被再次使用。如果直接把一个InputStream类的对象传递给一个接收者使用之后再传递给另外一个接收者,后者不能读取到流中的任何数据,因为流的当前读取位置已经到了末尾,也就是这个流只能使用一次,拦截器用了.(一万羊驼飞奔而过)

  5. 但是有一个问题?只能用一次为什么我还可以下载剩下的750B,RequestContext.getCurrentContext().setResponseBody(body);

  6. 乍一看这样似乎没有什么影响,但是String body = IOUtils.toString(stream);这样的转换真的没有问题吗?

  7. 1. IOUtils.toInputStream(body).equals(stream)//false
    2. RequestContext.getCurrentContext().setResponseDataStream(IOUtils.toInputStream(body));
    
  8. 于是我试了两个验证?结果是失败,经过了IOUtils的转换之后就流就不再是原先的流了,那少的24B也就是在这里丢的?

解决

判断如果是文件下载,直接返回,不读取流,不对数据做任何操作

END

耗时1.5天排查问题,写个博客,供大家参考,希望有所帮助

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章