文件下載時神祕消失的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天排查問題,寫個博客,供大家參考,希望有所幫助

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