当前位置: 首页 > news >正文

手把手教你Java文件断点下载

前言

互联网的连接速度慢且不稳定,有可能由于网络故障导致断开连接。

在客户端下载一个大对象时,因网络断开导致上传下载失败的概率就会变得不可忽视。

图片

客户端在GET对象请求时通过设置Range头部来告诉接口服务需要从什么位置开始输出对象的数据。

判断是否支持断点下载,根据文档:14.35.1 Byte Rangeshttps://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

// 直接判断是否有 Accept-Ranges = bytes boolean support = urlConnection.getHeaderField("Accept-Ranges").equals("bytes"); System.out.println("Partial content retrieval support = " + (support ? "Yes" : "No));

例如:

donald@donald-pro:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download HTTP/1.1 206 Accept-Ranges: bytes Content-Disposition: inline;filename=pom.xml Content-Range: bytes 0-9/13485 Content-Length: 10 Date: Mon, 01 Nov 2021 09:53:25 GMT

直接判断头部HEAD,例如:

HeadObject接口用于获取某个文件(Object)的元信息。使用此接口不会返回文件内容。

HEAD /ObjectName HTTP/1.1 Host: BucketName.oss-cn-hangzhou.aliyuncs.com Date: GMT Date Authorization: SignatureValue

需知,对应HTTP状态码:

  • 206 Partial ContentHTTP Range请求成功

  • 416 Requested Range Not Satisfiable statusHTTP Range请求超出界限

  • 200 OK:不支持范围请求

小结如下:

  1. HTTP范围请求:需要HTTP/1.1及之上支持,如果双端某一段低于此版本,则认为不支持。

  2. 通过响应头中的Accept-Ranges来确定是否支持范围请求。

  3. 通过在请求头中添加Range这个请求头,来指定请求的内容实体的字节范围。

  4. 在响应头中,通过Content-Range来标识当前返回的内容实体范围,并使用 Content-Length 来标识当前返回的内容实体范围长度。

  5. 在请求过程中,可以通过If-Range来区分资源文件是否变动,它的值来自ETag或者Last-Modifled。如果资源文件有改动,会重新走下载流程。

生产实战

开发也得依靠依据,设定好边界,才能掌控全局。

有现成的文档,来看阿里云文档https://help.aliyun.com/document_detail/39571.html

  • Range: bytes=0-499:表示第0~499字节范围的内容。

  • Range: bytes=500-999:表示第500~999字节范围的内容。

  • Range: bytes=-500:表示最后500字节的内容。

  • Range: bytes=500-:表示从第500字节开始到文件结束部分的内容。

  • Range: bytes=0-:表示第一个字节到最后一个字节,即完整的文件内容。

HTTP Range是否合法对应处理:

  • 如果HTTP Range请求合法,响应返回值为 206 并在响应头中包含Content-Range

  • 如果HTTP Range请求不合法,或者指定范围不在有效区间,会导致Range不生效,响应返回值为200,并传送整个Object内容。

如下为HTTP Range请求不合法的示例及错误说明: 假设Object资源大小为1000字节,Range有效区间为0~999

  • Range: byte=0-499:格式错误,byte应为bytes

  • Range: bytes=0-1000:末字节1000超出有效区间。

  • Range: bytes=1000-2000:指定范围超出有效区间。

  • Range: bytes=1000-:首字节超出有效区间。

  • Range: bytes=-2000:指定范围超出有效区间。

举一些栗子:

# 正常范围下载 donald@donald-pro:~$ curl -i --range 0-9 http://localhost:8080/file/chunk/download HTTP/1.1 206 Accept-Ranges: bytes Content-Disposition: inline;filename=Screen_Recording_20211101-162729_Settings.mp4 Content-Range: bytes 0-9 Content-Type: application/force-download;charset=UTF-8 Content-Length: 16241985 Date: Wed, 03 Nov 2021 09:50:50 GMT

服务端 - 业务开发

这里以SpringBoot为栗子:

  1. 对外支持range下载

  2. 底层存储:使用ceph

  3. Controller如下:

@Slf4j @RestController public class Controller { @Autowired private FileService fileService; /** * 下载文件 * * 对外提供 * * @param fileId 文件Id * @param token token * @param accountId 帐号Id * @param response 响应 */ @GetMapping("/oceanfile/download") public void downloadOceanfile(@RequestParam String fileId, @RequestHeader(value = "Range") String range, HttpServletResponse response) { this.fileService.downloadFile(fileId, response, range); } }
  1. Service如下:

@Slf4j @Service public class FileService { @Autowired private CephUtils cephUtils; /** * 直接下载文件 * * Tips: 支持断点下载 * @param fileId 文件Id * @param response 返回 * @param range 范围 */ public void downloadFile(String fileId, HttpServletResponse response, String range) { // 根据 fileId 获取文件信息 FileInfo fileInfo = getFileInfo(fileId); String bucketName = fileInfo.getBucketName(); String relativePath = fileInfo.getRelativePath(); // 处理 range,范围信息 RangeDTO rangeInfo = executeRangeInfo(range, fileInfo.getFileSize()); // rangeInfo = null,直接下载整个文件 if (Objects.isNull(rangeInfo)) { cephUtils.downloadFile(response, bucketName, relativePath); return; } // 下载部分文件 cephUtils.downloadFileWithRange(response, bucketName, relativePath, rangeInfo); } private RangeDTO executeRangeInfo(String range, Long fileSize) { if (StringUtils.isEmpty(range) || !range.contains("bytes=") || !range.contains("-")) { return null; } long startByte = 0; long endByte = fileSize - 1; range = range.substring(range.lastIndexOf("=") + 1).trim(); String[] ranges = range.split("-"); if (ranges.length <= 0 || ranges.length > 2) { return null; } try { if (ranges.length == 1) { if (range.startsWith("-")) { //1. 如:bytes=-1024 从开始字节到第1024个字节的数据 endByte = Long.parseLong(ranges[0]); } elseif (range.endsWith("-")) { //2. 如:bytes=1024- 第1024个字节到最后字节的数据 startByte = Long.parseLong(ranges[0]); } } else { //3. 如:bytes=1024-2048 第1024个字节到2048个字节的数据 startByte = Long.parseLong(ranges[0]); endByte = Long.parseLong(ranges[1]); } } catch (NumberFormatException e) { startByte = 0; endByte = fileSize - 1; } if (startByte >= fileSize) { log.error("range error, startByte >= fileSize. " + "startByte: {}, fileSize: {}", startByte, fileSize); return null; } return new RangeDTO(startByte, endByte); } }

以上内容,大家可以收藏起来,如果以后遇到这样的场景,分分钟搞定!

http://www.jsqmd.com/news/165739/

相关文章:

  • 【译】Visual Studio —— 为现代开发的速度而打造
  • 什么是网络安全?如何系统学习?这里有一份清晰的自学路径图
  • Vue2 中 Options API:组织组件逻辑的主要方式
  • 【ACM出版、见刊检索稳定 | 南京航空航天大学主办,高校背书 | EI&Scopus检索稳定 | 合作Scopus期刊推荐】第二届数字化社会、信息科学与风险管理国际学术会议(ICDIR 2026)
  • 靠谱代理记账公司推荐:账务处理与财务咨询的优质之选 - 工业品网
  • 2025白箱板纸品质厂家TOP5权威推荐:推荐白箱板纸厂深度测评 - 工业设备
  • 美容仪排行榜:5大热门机型硬核横评,Ulike童颜超光炮成全能首选 - 品牌企业推荐师(官方)
  • 工程施工现场电力支持:低噪音发电机出租厂商TOP5建议 - 深度智识库
  • 2025年终固相萃取仪大盘点:SPE设备/正压固相萃取装置推荐品牌厂家及选购建议 - 品牌推荐大师1
  • 先睹为快 | 2026年3月国际学术会议一览表
  • 柴油发电机出租市场观察:2025-2026值得关注的TOP5发电机租赁服务商测评解析 - 深度智识库
  • 2025北京资质齐全的汽车贴膜公司TOP5权威推荐:诚信口碑企业甄选指南 - 工业品网
  • TEBBIT 交易所:在数字资产时代构筑信任与性能的新标杆
  • 收藏!2025大模型薪资狂欢全解析:小白程序员入门最后黄金窗口
  • 揭秘ReAct框架:AI Agent的’思考-行动-观察’三步舞,高效开发的秘诀!
  • 基于spring boot的钢材销售管理系统的设计与实现
  • JS知识回顾(下)-DOM
  • 2025高新技术企业申报哪家合适?TOP5权威推荐:精准匹配需求,助力企业高效拿证 - 工业品牌热点
  • 2025年电子皮带秤维修权威推荐榜单:电子皮带秤改造/电子皮带秤安装/电子皮带秤批发/电子皮带秤定制/皮带秤称重仪表源头厂家精选 - 品牌推荐官
  • 2025年压力机厂家实力推荐榜:滕州市大正液压设备有限公司二梁四柱/三梁四柱/伺服压力机全系供应 - 品牌推荐官
  • 基于Spring Boot的教育平台设计与实现
  • markdown的教程
  • 基于Spring boot的洛川县苹果销售管理平台
  • JS知识回顾(中)-BOM
  • RAG实战指南:告别大模型幻觉,提升回答准确率!
  • 基于Spring Boot的煤矿信息管理系统
  • 前端性能优化工程化落地指南:从基础实践到极致性能突破
  • 竞速物流:化妆品寄中国香港的省心之选,专业解锁国际邮寄新体验 - 深度智识库
  • 动态特征选择稳住房颤预警
  • 京帆合赢代理记账:靠谱之选,为企业财税保驾护航 - mypinpai