zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    git提交代码到github步骤
    HTML前端标签
    16-类视图
    15-auth系统与类视图
    14-中间件和上下文处理器
    13-会话技术及表单(cookies和session)
    07-Python Django view 两种return 方法
    10-请求与响应和HTML中的from表单
    09-表关联对象及多表查询
    08-常用查询及表关系的实现
  • 原文地址:https://www.cnblogs.com/liaojie970/p/5013790.html
Copyright © 2011-2022 走看看