斷點續傳下載原理實現

来源:http://www.cnblogs.com/peachyy/archive/2017/09/05/7482222.html
-Advertisement-
Play Games

需求背景 動態創建的文件下載的時候希望瀏覽器顯示下載進度 動態創建的文件希望能夠分段下載 HTTP斷點續傳報文 要實現HTTP斷點續傳必須要簡單瞭解以下幾個報文。 Accept-Ranges 告訴客戶端(瀏覽器..)伺服器端支持斷點續傳 伺服器端返回 Range 客戶端告訴伺服器端從指定的的位置/範 ...


需求背景

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

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測試分段下載。

 

笑笑笑技術圈


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 for v in d.values(): for k,v in d.items(): for a in 'adfa': #判斷對象是否可迭代 from collections import Iterable isinstance('abc',Iterable) #添加下標 for x,y in ...
  • isinstance和issubclass isinstance()判斷一個對象是不是這個類的對象,傳兩個參數(對象,類) issubclass()判斷一個類是不是另一類的子類,傳兩個參數(子類,父類) class Foo: pass class Son(Foo): pass s = Son() # ...
  • 因為公司的需求,我使用了velocity模板進行文件生成。在這裡先記錄一下使用velocity模板時的一些小技巧: 1.截取字元串 註意,(1)需要使用.length()獲取字元串長度; (2)最好使用“+(-1)”的形式代替“-1”,因為velocity中對減號“-”的支持不友好,容易混淆。 2. ...
  • 先說下我自己的理解,一般而言,JSON字元串要轉為java對象需要自己寫一個跟JSON一模一樣的實體類bean,然後用bean.class作為參數傳給對應的方法,實現轉化成功。 上述這種方法太麻煩了。其實有一種東西叫jsonObject可以直接不用新建實體類bean,而實現轉化,先說org.json ...
  • 一、subprocess模塊 os.system 輸出命令結果到屏幕,返回命令執行狀態 使用方式為: res=os.system('dir') 結果顯示為: 結果顯示為: 驅動器 C 中的捲是 WINDOWS 捲的序列號是 CC81-05A6 C:\Users\zhao 的目錄 2017/07/12 ...
  • 1 _path = $path; 20 $this->_allowType = array( 21 // images 22 'bmp' => 'image/x-ms-bmp', 23 'jpg' => 'image/jpeg', 24 '... ...
  • 1.java.net.Socket;套接字、封裝了TCP通訊協議,使用它可以基於TCP與遠端電腦上的服務端應用程式鏈接併進行通訊。 實例化Socket就是與伺服器端建立連接的過程。這裡需要傳入兩個參數來指定服務端地址信息: 參數1:伺服器端電腦地址 參數2:運行在伺服器電腦上的服務端應用程式打 ...
  • 一、Debug開篇 二、基本用法&快捷鍵 三、變數查看 四、計算表達式 五、智能步入 六、斷點條件設置 七、多線程調試 八、回退斷點 九、中斷Debug 十、附:JRebel激活 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...