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

发布时间 - 2026-01-11 03:07:24    点击率:

需求背景

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

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

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测试分段下载。

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


# 断点续传  # 原理  # 很简单的Java断点续传实现原理  # Java如何实现HTTP断点续传功能  # 客户端  # 几个  # 给大家  # 第一个  # 就能  # 是从  # 希望能  # 然后再  # 可以使用  # 这篇  # 小编  # 服务端  # 大家多多  # 流到  # 不通过  # 数据格式  # 在整个  # 必须要  # zipFile 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 东莞市网站制作公司有哪些,东莞找工作用什么网站好?  昵图网官方站入口 昵图网素材图库官网入口  JavaScript如何操作视频_媒体API怎么控制播放  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  齐河建站公司:营销型网站建设与SEO优化双核驱动策略  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程  重庆市网站制作公司,重庆招聘网站哪个好?  javascript中闭包概念与用法深入理解  标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南  Python函数文档自动校验_规范解析【教程】  高防服务器如何保障网站安全无虞?  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  javascript中的数组方法有哪些_如何利用数组方法简化数据处理  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  制作电商网页,电商供应链怎么做?  使用Dockerfile构建java web环境  如何为不同团队 ID 动态生成多个“认领值班”按钮  软银砸40亿美元收购DigitalBridge 强化AI资料中心布局  Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率  Laravel怎么判断请求类型_Laravel Request isMethod用法  Linux网络带宽限制_tc配置实践解析【教程】  Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】  INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】  高防网站服务器:DDoS防御与BGP线路的AI智能防护方案  Laravel如何处理和验证JSON类型的数据库字段  php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】  JavaScript如何实现继承_有哪些常用方法  node.js报错:Cannot find module &#39;ejs&#39;的解决办法  Laravel API资源类怎么用_Laravel API Resource数据转换  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  长沙做网站要多少钱,长沙国安网络怎么样?  laravel服务容器和依赖注入怎么理解_laravel服务容器与依赖注入解析  如何快速搭建二级域名独立网站?  Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】  Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】  做企业网站制作流程,企业网站制作基本流程有哪些?  Laravel API资源(Resource)怎么用_格式化Laravel API响应的最佳实践  手机软键盘弹出时影响布局的解决方法  常州企业网站制作公司,全国继续教育网怎么登录?  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  太平洋网站制作公司,网络用语太平洋是什么意思?  Python图片处理进阶教程_Pillow滤镜与图像增强  Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】  Laravel如何使用Service Container和依赖注入?(代码示例)  西安专业网站制作公司有哪些,陕西省建行官方网站?  Laravel如何创建自定义Artisan命令?(代码示例)  Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道  如何在橙子建站上传落地页?操作指南详解  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】