基于断点续传下载原理的实现

 更新时间:2017年09月06日 08:36:51   作者:peachyy  
下面小编就为大家带来一篇基于断点续传下载原理的实现。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

需求背景

动态创建的文件下载的时候希望浏览器显示下载进度

动态创建的文件希望能够分段下载

HTTP断点续传报文

要实现HTTP断点续传必须要简单了解以下几个报文。

Accept-Ranges 告诉客户端(浏览器..)服务器端支持断点续传 服务器端返回

Range 客户端告诉服务器端从指定的的位置/范围(这里值字节数)下载资源 客户端发出

Content-Range 服务器端告诉客户端响应的数据信息,在整个返回体中本部分的字节位置 服务器端返回

ETag 资源标识 非必须 服务器端返回

Last-Modified 资源最后一次更新的时间 非必须 服务器端返回

Range 的范围格式

表示0-499个字节范围:Range: bytes=0-499

表示最后500个字节范围:Range: bytes=-500

表示500字节开始到结束范围:Range: bytes=500-

表示第一个和最后一个字节:Range: bytes=0-0,-1

表示同时指定几个范围:Range: bytes=500-600,601-999

Content-Range 的数据格式

Content-Range: bytes 0-499/22036 :表示返回0-499字节范围数据 资源一共22036个字节

原理

客户端发起请求 设置Range指定开始字节数或结束字节数 如果是从0开始也可以不用设置。

服务器端检查到客户端Range头 解析开始字节数以及结束字节数 并返回报文头 Accept-Ranges表示支持断点续传,Content-Range记录该次向客户端写入流的位置信息,然后再写入流到客户端。

服务端可以使用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测试分段下载。

以上这篇基于断点续传下载原理的实现就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot请求处理之常用参数注解介绍与源码分析

    SpringBoot请求处理之常用参数注解介绍与源码分析

    SpringBoot是一种整合Spring技术栈的方式(或者说是框架),同时也是简化Spring的一种快速开发的脚手架,本篇让我们一起学习请求处理、常用注解和方法参数的小技巧
    2022-10-10
  • 详解Java如何实现在PDF中插入,替换或删除图像

    详解Java如何实现在PDF中插入,替换或删除图像

    图文并茂的内容往往让人看起来更加舒服,如果只是文字内容的累加,往往会使读者产生视觉疲劳。搭配精美的文章配图则会使文章内容更加丰富。那我们要如何在PDF中插入、替换或删除图像呢?别担心,今天为大家介绍一种高效便捷的方法
    2023-01-01
  • mybatis一级缓存和二级缓存的区别及说明

    mybatis一级缓存和二级缓存的区别及说明

    这篇文章主要介绍了mybatis一级缓存和二级缓存的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java详细讲解IO流的Writer与Reader操作

    Java详细讲解IO流的Writer与Reader操作

    Writer与Reader类不能直接调用,需要使用多带的方法调用它们的子类,在他们的前边加上一个File即可如(FileWriter或FileReader)的多态方法进行其调用,并且他们也是抽象类调用需要连接接口Exception,它们的优点在于可以直接写入或读出内容,不需要使用byte转八进制
    2022-05-05
  • JavaFX桌面应用未响应问题解决方案

    JavaFX桌面应用未响应问题解决方案

    这篇文章主要介绍了JavaFX桌面应用未响应问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Eureka源码阅读Client启动入口注册续约及定时任务

    Eureka源码阅读Client启动入口注册续约及定时任务

    这篇文章主要为大家介绍了Eureka源码阅读Client启动入口注册续约及定时任务示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • JavaWeb Servlet技术及其应用实践

    JavaWeb Servlet技术及其应用实践

    这篇文章主要介绍了JavaWeb Servlet技术,Servlet指在服务器端执行的一段Java代码,可以接收用户的请求和返回给用户响应结果,感兴趣想要详细了解可以参考下文
    2023-05-05
  • Struts2学习教程之Action类如何访问WEB资源

    Struts2学习教程之Action类如何访问WEB资源

    这篇文章主要给大家介绍了关于Struts2学习教程之Action类如何访问WEB资源的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-04-04
  • 基于SpringBoot和Vue实现头像上传与回显功能

    基于SpringBoot和Vue实现头像上传与回显功能

    在现代Web应用中,用户个性化体验尤为重要,其中头像上传与回显是一个常见的功能需求,本文将详细介绍如何使用Spring Boot和Vue.js构建一个前后端协同工作的头像上传系统,并实现图片的即时回显,需要的朋友可以参考下
    2024-08-08
  • img 加载网络图片失败 显示默认图片的方法

    img 加载网络图片失败 显示默认图片的方法

    下面小编就为大家带来一篇img 加载网络图片失败 显示默认图片的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05

最新评论