java 實現斷點續傳


 請求頭一:
>>>>>>>>>>>>>>>>>>>>>>>>
range:bytes=1024-    //斷點續傳請求必須包含該請求頭
host:192.168.118.120:8888
accept:*/*
>>>>>>>>>>>>>>>>>>>>>>>>

響應頭一:
>>>>>>>>>>>>>>>>>>>>>>>>
Server: Apache-Coyote/1.1
Content-Disposition: attachment; filename=WebGoat-OWASP_Developer-5.2.zip
Accept-Ranges: bytes
Content-Range: bytes 1024-304974591/304974592
Content-Type: application/x-download;charset=utf-8
Content-Length: 304973568   //需要特別注意這里長度值為請求需要的長度,即304974591 - 1024
>>>>>>>>>>>>>>>>>>>>>>>>

請求頭二:
>>>>>>>>>>>>>>>>>>>>>>>>
range:bytes=10-1033  //斷點續傳請求必須包含該請求頭
host:192.168.118.120:8888
accept:*/*
>>>>>>>>>>>>>>>>>>>>>>>>

響應頭二:
>>>>>>>>>>>>>>>>>>>>>>>>
Server: Apache-Coyote/1.1
Content-Disposition: attachment; filename=WebGoat-OWASP_Developer-5.2.zip
Accept-Ranges: bytes
Content-Range: bytes 10-1033/304974592
Content-Type: application/x-download;charset=utf-8
Content-Length: 1024  //需要特別注意這里長度值為請求需要的長度,即1033- 10
>>>>>>>>>>>>>>>>>>>>>>>>

/**
     * 下載服務器已存在的文件,支持斷點續傳
     *
     * @param request
     *            請求對象
     * @param response
     *            響應對象
     * @param path
     *            文件路徑(絕對)
     */
    public static void download(HttpServletRequest request, HttpServletResponse response, File proposeFile) {
        LOGGER.debug("下載文件路徑:" + proposeFile.getPath());
        InputStream inputStream = null;
        OutputStream bufferOut = null;
        try {
            // 設置響應報頭
            long fSize = proposeFile.length();
            response.setContentType("application/x-download");
            // Content-Disposition: attachment; filename=WebGoat-OWASP_Developer-5.2.zip
            response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(proposeFile.getName(), ENCODING));
            // Accept-Ranges: bytes
            response.setHeader("Accept-Ranges", "bytes");
            long pos = 0, last = fSize - 1, sum = 0;//pos開始讀取位置;  last最后讀取位置;  sum記錄總共已經讀取了多少字節
            if (null != request.getHeader("Range")) {
                // 斷點續傳
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                try {
                    // 情景一:RANGE: bytes=2000070- 情景二:RANGE: bytes=2000070-2000970
                    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") + " is not Number!");
                    pos = 0;
                }
            }
            long rangLength = last - pos + 1;// 總共需要讀取的字節
            // Content-Range: bytes 10-1033/304974592
            String contentRange = new StringBuffer("bytes ").append(pos).append("-").append(last).append("/").append(fSize).toString();
            response.setHeader("Content-Range", contentRange);
            // Content-Length: 1024
            response.addHeader("Content-Length", String.valueOf(rangLength));

            // 跳過已經下載的部分,進行后續下載
            bufferOut = new BufferedOutputStream(response.getOutputStream());
            inputStream = new BufferedInputStream(new FileInputStream(proposeFile));
            inputStream.skip(pos);
            byte[] buffer = new byte[1024];
            int length = 0;
            while (sum < rangLength) {
                length = inputStream.read(buffer, 0, ((rangLength - sum) <= buffer.length ? ((int) (rangLength - sum)) : buffer.length));
                sum = sum + length;
                bufferOut.write(buffer, 0, length);
            }
        } catch (Throwable e) {
            if (e instanceof ClientAbortException) {
                // 瀏覽器點擊取消
                LOGGER.info("用戶取消下載!");
            } else {
                LOGGER.info("下載文件失敗....");
                e.printStackTrace();
            }
        } finally {
            try {
                if (bufferOut != null) {
                    bufferOut.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

開發中遇到的一個錯誤提示:

org.apache.catalina.connector.ClientAbortException: Connection reset by peer: socket write error

該錯誤的原因就是因為上面的Content-Length: 1024 與請求頭重請求的長度不一致,導致了請求端拒絕了

 

http斷點續傳原理:http頭 Range、Content-Range

所謂斷點續傳,也就是要從文件已經下載的地方開始繼續下載。在以前版本的 HTTP 協議是不支持斷點的,HTTP/1.1 開始就支持了。一般斷點下載時才用到 Range 和 Content-Range 實體頭。

Range 

用於請求頭中,指定第一個字節的位置和最后一個字節的位置,一般格式:

Range:(unit=first byte pos)-[last byte pos] 

Content-Range

用於響應頭,指定整個實體中的一部分的插入位置,他也指示了整個實體的長度。在服務器向客戶返回一個部分響應,它必須描述響應覆蓋的范圍和整個實體長度。一般格式: 

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth] 

請求下載整個文件: 

  1. GET /test.rar HTTP/1.1 
  2. Connection: close 
  3. Host: 116.1.219.219 
  4. Range: bytes=0-801 //一般請求下載整個文件是bytes=0- 或不用這個頭

一般正常回應

  1. HTTP/1.1 200 OK 
  2. Content-Length: 801      
  3. Content-Type: application/octet-stream 
  4. Content-Range: bytes 0-800/801 //801:文件總大小

 

 

以下是摘取網絡中的一段內容,並進了修改:原始的內容有誤導致被坑

斷點續傳的原理

其實斷點續傳的原理很簡單,就是在 Http 的請求上和一般的下載有所不同而已。       
打個比方,瀏覽器請求服務器上的一個文時,所發出的請求如下:       
假設服務器域名為 wwww.sjtu.edu.cn,文件名為 down.zip。
GET /down.zip HTTP/1.1        
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-        
excel, application/msword, application/vnd.ms-powerpoint, */*        
Accept-Language: zh-cn        
Accept-Encoding: gzip, deflate        
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)        
Connection: Keep-Alive     

服務器收到請求后,按要求尋找請求的文件,提取文件的信息,然后返回給瀏覽器,返回信息如下:

200        
Content-Length=106786028        
Accept-Ranges=bytes        
Date=Mon, 30 Apr 2001 12:56:11 GMT        
ETag=W/"02ca57e173c11:95b"       
Content-Type=application/octet-stream        
Server=Microsoft-IIS/5.0        
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT     

所謂斷點續傳,也就是要從文件已經下載的地方開始繼續下載。所以在客戶端瀏覽器傳給 Web 服務器的時候要多加一條信息 -- 從哪里開始。       
下面是用自己編的一個"瀏覽器"來傳遞請求信息給 Web 服務器,要求從 2000070 字節開始。       
GET /down.zip HTTP/1.0        
User-Agent: NetFox        
RANGE: bytes=2000070-        
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2     

仔細看一下就會發現多了一行 RANGE: bytes=2000070-        
這一行的意思就是告訴服務器 down.zip 這個文件從 2000070 字節開始傳,前面的字節不用傳了。       
服務器收到這個請求以后,返回的信息如下:       
206        
Content-Length=106585958       
Content-Range=bytes 2000070-106786027/106786028        
Date=Mon, 30 Apr 2001 12:55:20 GMT        
ETag=W/"02ca57e173c11:95b"       
Content-Type=application/octet-stream        
Server=Microsoft-IIS/5.0        
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT     

和前面服務器返回的信息比較一下,就會發現變化:    

Content-Length=106585958   
Content-Range=bytes 2000070-106786027/106786028        
返回的代碼也改為 206 了,而不再是 200 了。    

知道了以上原理,就可以進行斷點續傳的編程了

 

Java 實現斷點續傳的關鍵幾點

  1. (1) 用什么方法實現提交 RANGE: bytes=2000070-。
    當然用最原始的 Socket 是肯定能完成的,不過那樣太費事了,其實 Java 的 net 包中提供了這種功能。代碼如下:

    URL url = new URL("http://www.sjtu.edu.cn/down.zip");
    HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();

    // 設置 User-Agent
    httpConnection.setRequestProperty("User-Agent","NetFox");
    // 設置斷點續傳的開始位置
    httpConnection.setRequestProperty("RANGE","bytes=2000070");
    // 獲得輸入流
    InputStream input = httpConnection.getInputStream();

    從輸入流中取出的字節流就是 down.zip 文件從 2000070 開始的字節流。 大家看,其實斷點續傳用 Java 實現起來還是很簡單的吧。 接下來要做的事就是怎么保存獲得的流到文件中去了。

  2. 保存文件采用的方法。
    我采用的是 IO 包中的 RandAccessFile 類。
    操作相當簡單,假設從 2000070 處開始保存文件,代碼如下:
    RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");
    long nPos = 2000070;
    // 定位文件指針到 nPos 位置
    oSavedFile.seek(nPos);
    byte[] b = new byte[1024];
    int nRead;
    // 從輸入流中讀入字節流,然后寫到文件中
    while((nRead=input.read(b,0,1024)) > 0)
    {
    oSavedFile.write(b,0,nRead);
    }

怎么樣,也很簡單吧。 接下來要做的就是整合成一個完整的程序了。包括一系列的線程控制等等。

注:轉載http://www.ibm.com/developerworks/cn/java/joy-down/index.html


免責聲明!

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



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