斷點續傳下載原理實現


需求背景

  • 動態創建的文件下載的時候希望瀏覽器顯示下載進度
  • 動態創建的文件希望能夠分段下載

HTTP斷點續傳報文

要實現HTTP斷點續傳必須要簡單了解以下幾個報文。

  • Accept-Ranges 告訴客戶端(瀏覽器..)服務器端支持斷點續傳 服務器端返回
  • Range 客戶端告訴服務器端從指定的的位置/范圍(這里值字節數)下載資源 客戶端發出
  • Content-Range 服務器端告訴客戶端響應的數據信息,在整個返回體中本部分的字節位置 服務器端返回
  • ETag 資源標識 非必須 服務器端返回
  • Last-Modified 資源最后一次更新的時間 非必須 服務器端返回

Range 的范圍格式

  1. 表示0-499個字節范圍:Range: bytes=0-499
  2. 表示最后500個字節范圍:Range: bytes=-500
  3. 表示500字節開始到結束范圍:Range: bytes=500-
  4. 表示第一個和最后一個字節:Range: bytes=0-0,-1
  5. 表示同時指定幾個范圍:Range: bytes=500-600,601-999

Content-Range 的數據格式

Content-Range: bytes 0-499/22036 :表示返回0-499字節范圍數據 資源一共22036個字節

原理

  1. 客戶端發起請求 設置Range指定開始字節數或結束字節數 如果是從0開始也可以不用設置。
  2. 服務器端檢查到客戶端Range頭 解析開始字節數以及結束字節數 並返回報文頭 Accept-Ranges表示支持斷點續傳,Content-Range記錄該次向客戶端寫入流的位置信息,然后再寫入流到客戶端。
  3. 服務端可以使用ETag Last-Modified 標記一下資源是否被修改。作一些驗證工作,如果驗證不通過則返回錯誤,非必須項。

java實現

 OutputStream os=null;
    InputStream inputStream =null;
    File zipFile=null;
    try{
        long zipStart=System.currentTimeMillis();
        zipFile=createFile();//動態根據業務創建文件
        if(logger.isInfoEnabled()){
            logger.info(String.format("壓縮ZIP 花費時間 %s(s) ",
        (System.currentTimeMillis()-zipStart)/1000));
        }
        if (zipFile.exists()) {
            long downloadStart=System.currentTimeMillis();
            inputStream= new BufferedInputStream(new FileInputStream(zipFile));
            response.reset();
            os=new BufferedOutputStream(response.getOutputStream());
            String userAgent = request.getHeader("USER-AGENT");
            String fileName=zipFile.getName();
            if (null != userAgent && -1 != userAgent.indexOf("MSIE")) {
                fileName = URLEncoder.encode(fileName, "UTF8");
            } else if (null != userAgent && -1 != userAgent.indexOf("Mozilla")) {
                fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
            }
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Disposition", 
        "attachment;filename="+ fileName);
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            long pos = 0, fileSize=zipFile.length(),
    last=fileSize-1;
            response.setHeader("ETag",zipFile.getName().
         concat(Objects.toString(fileSize))
                    .concat("_").concat(Objects.toString(zipFile.lastModified())));
            response.setDateHeader("Last-Modified",zipFile.lastModified());
            response.setDateHeader("Expires",
            System.currentTimeMillis()+1000*60*60*24);
            if (null != request.getHeader("Range")) {
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                try {
                    // 暫時只處理這2種range格式 1、RANGE: bytes=111- 2、Range: bytes=0-499
                    String numRang = request.getHeader("Range")
            .replaceAll("bytes=", "");
                    String[] strRange = numRang.split("-");
                    if (strRange.length == 2) {
                        pos = Long.parseLong(strRange[0].trim());
                        last = Long.parseLong(strRange[1].trim());
                    } else {
                        pos = Long.parseLong(numRang.replaceAll("-", "").trim());
                    }
                } catch (NumberFormatException e) {
                    logger.error(request.getHeader("Range") + " error");
                    pos = 0;
                }
            }
            long rangLength = last - pos + 1;
            String contentRange = new StringBuffer("bytes ").
            append(String.valueOf(pos)).
            append("-").append(last).append("/").
            append(String.valueOf(fileSize)).toString();
            response.setHeader("Content-Range", contentRange);
            response.addHeader("Content-Length",Objects.toString(rangLength));
            if(pos>0){
                inputStream.skip(pos);
            }
            byte[] buffer = new byte[1024*512];//每次以512KB 0.5MB的流量下載
            int length = 0,sendTotal=0;
            while (sendTotal < rangLength && length!=-1) {
                length = inputStream.read(buffer, 0,
        ((rangLength - sendTotal) <= buffer.length ?
                        ((int) (rangLength - sendTotal)) : buffer.length));
                sendTotal = sendTotal + length;
                os.write(buffer, 0, length);
            }
            if(os!=null){
                os.flush();
            }
            if(logger.isInfoEnabled()){
                logger.info(String.format("下載 花費時間 %s(s) ",
        (System.currentTimeMillis()-downloadStart)/1000));
            }
        }
    }catch (Exception e){
        if(StringUtils.endsWithIgnoreCase(e.getMessage(),"Broken pipe")){
            logger.error("用戶取消下載");
        }
        logger.error(e.getMessage(),e);
    }finally {
        if(os!=null){
            try{
                os.close();
            }catch (Exception e){}
        }
        if(inputStream!=null){
            try{
                IOUtils.closeQuietly(inputStream);
            }catch (Exception e){}
        }
    }
}

 

比如google瀏覽器下載的時候就能看到下載進度以及暫停下載和恢復下載操作,也可以設置Range測試分段下載。

 

笑笑笑技術圈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM