首頁  >  文章  >  Java  >  Java實作斷點續傳下載原理的範例分享

Java實作斷點續傳下載原理的範例分享

黄舟
黄舟原創
2017-09-06 09:54:091972瀏覽

需求背景

  • 動態建立的檔案下載的時候希望瀏覽器顯示下載進度

  • 動態建立的檔案希望能夠分段下載

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. 客戶端啟動請求設定#位元組數或結束位元組數如果是從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測試分段下載。


以上是Java實作斷點續傳下載原理的範例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn