我又学会了使用Range实现网络文件下载的断点续传

文章目录

  • 前言
  • 1、Range请求头
    • 1.1、概述
    • 1.2、使用限制
    • 1.3、范围请求
    • 1.4、预防资源变更
  • 2、断点续传下载实现
    • 2.1、流程设计
    • 2.2、代码实现
    • 2.3、运行结果
  • 3、RandomAccessFile
  • 4、思维拓展
  • 参考资料

前言

在某次摸鱼的过程中,老大突然后面冒出来说要做一个拉取文件到本地的需求(写的时候疯狂回头🤡),当时心想这简单,不就一个HttpClient或者RestTemplate的事情嘛,很快一两天就给整出来心满意足的提交了。
不出意外的话要出意外了,老大看了一眼我的代码就问:“你没有做断点续传吗”,我:“啊?”(好吧得加班了)
因为当时我还没有玩过断点续传,老大就和我提了一嘴可以考虑使用Range请求头来实现,在我巴拉巴拉的有一两天后,最终断点续传版本的网络文件下载功能就出来了(工具类加一✔)

省流:本文章除了断点续传的视线,还发散介绍了Range请求头和一些零零散散的其他小东西,不感兴趣的小伙伴可直接跳至“断点续传下载实现”。

1、Range请求头

1.1、概述

顾名思义,HTTP/1.1 Range请求头代表发送范围获取数据的请求,要求服务器仅向客户端回传HTTP消息的一部分,格式以及示例如下:

Range: <数据格式>=<数据开始的索引位置>-<数据结束的索引位置>
# 1. 请求从0至500的byte数据:
Range: bytes=0-500
# 2. 请求第500个byte以后的全部数据:
Range: bytes=501-
# 3. 请求最后500个byte的数据:
Range:bytes=-500
# 4. 请求多个分段时,各分段以,分割:
Range: bytes=0-100,101-200

1.2、使用限制

正是得益于Range请求头的这种特性,因此在很多断点续传的场景下都能看到它的身影,但在使用之前需要确定我们的请求中能否使用该请求头。
不知道大家伙有没有发现前面提及Range请求头的时候,我添加上了一个前缀“HTTP/1.1”,这是因为只在 HTTP/1.1(RFC2616) 之上,才支持范围请求。所以如果客户端或者服务端两端的某一端低于 HTTP/1.1,我们就不应该使用范围请求的功能。我们可以通过 curl -i命令来测试一下是否支持范围请求:

curl -i https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png

如果 HTTP 响应中存在 Accept-Ranges 标头,并且其值不是 none,那么该服务器支持范围请求。
image.png

1.3、范围请求

当我们确定可以使用范围请求后,我们便可愉快的开始发起请求啦。这里以获取前1024bytes数据为例:Range 还有几种不同的方式来限定范围,可以根据需要灵活定制:

  1. 500-1000:指定开始和结束的范围,一般用于多线程分片下载。
  2. 500-:指定开始区间,一直传递到结束。这个就比较适用于断点续传、或者在线播放等等。
  3. -500:无开始区间,只意思是需要最后 500 bytes 的内容实体。
  4. 100-300,1000-3000:指定多个范围,这种方式使用的场景很少,了解一下就好了。
curl -i https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png -H "Range: bytes=0-1023"

在响应中我们可以很明确看到206响应码和Content-Range范围响应数据:

  1. HTTP 206 Partial Content 成功状态响应代码表示请求已成功,进一步了解可查看206 Partial Content - HTTP | MDN
  2. Content-Range标记当前传递的内容实体范围和总长度,单位是bytes

image.png

1.4、预防资源变更

我们在网上偶尔也会发现一个现象:下载大尺寸资源的时候,偶尔中间暂停过再重新下载,资源又重头开始了下载。这看似断点续传功能失效了,但实际上并不一定,可能是在这期间该资源发生了变更。
针对以上情况,可以使用If-Range请求头标记创建具有条件的范围请求,条件没有得到满足,服务器将返回完整的资源以及 200 OK 状态。

进一步了解可查看If-Range - HTTP | MDN

2、断点续传下载实现

2.1、流程设计

基于前面对Range请求头及与其相关内容的分析,假设现在需要下载uTools工具(打钱Please💰),借助Range实现断点续传下载的流程设计如下:

  1. 获取下载地址链接;
  2. 获取文件的断点(起始点或续点);
  3. 设置Range及相关请求头;
  4. 下载完毕,标记文件完成(以重命名文件作标记)

叠甲:下面的代码实现是为了演示做了修改的代码,大家伙在实现时可根据具体情况具体实现,这里提供几个可更换的点:

  • 记录最新修改时间可使用其它方式存储替代Map;
  • 文件下载方式视情况而定;
  • 文件下载完毕标记等……

2.2、代码实现

public class FileUtils {public static void main(String[] args) {FileUtils.processNetFile("https://open.u-tools.cn/download/uTools-5.0.0.exe","uTools-5.0.0.exe","F:\\download");}// 用于存放文件对应的最新修改时间private static final Map<String, String> fileRangeModifiedMap = new HashMap<>();/*** 处理网络文件下载操作* @author xbaoziplus* @param downloadUrl       网络文件下载地址* @param filename          文件名,含后缀* @param storageDirectory  文件存储目录路径* @createTime 2024/5/7 15:39*/public static void processNetFile(String downloadUrl, String filename, String storageDirectory) {if (StringUtils.isAnyBlank(downloadUrl, filename)) {throw new RuntimeException("param is blank");}try (CloseableHttpClient httpClient = HttpClients.createDefault()) {HttpGet httpGet = new HttpGet(downloadUrl);String filePath = storageDirectory + '/' + filename;String tmpFilePath = storageDirectory + '/' + filename + ".downloading";// 文件对象及断点位置初始化File targetFile = new File(tmpFilePath);long downloadedByte = 0;if (targetFile.exists()) {downloadedByte = targetFile.length();} else {if (!targetFile.getParentFile().exists()) {targetFile.getParentFile().mkdirs();}targetFile.createNewFile();}// 如果文件存在且大小不为0,添加Range请求头if (downloadedByte > 0) {httpGet.addHeader("Range", "bytes=" + downloadedByte + "-");if (fileRangeModifiedMap.get(filename) != null) {httpGet.addHeader("If-Range", fileRangeModifiedMap.get(filename));}}// 请求下载地址HttpResponse response = httpClient.execute(httpGet);// 获取并保存last-modified头值String lastModified = response.getFirstHeader("Last-Modified").getValue();fileRangeModifiedMap.put(filename, lastModified);long contentLength = Long.parseLong(response.getFirstHeader("Content-Length").getValue()) + downloadedByte;System.out.println("总大小:" + contentLength + " bytes");try (InputStream is = response.getEntity().getContent();RandomAccessFile raf = new RandomAccessFile(targetFile, "rwd")) {// 将写文件指针移到文件尾。raf.seek(downloadedByte);// 设置每次磁盘写入最大1Mbyte[] buffer = new byte[1024*1024];int len;while ((len = is.read(buffer)) != -1) {raf.write(buffer, 0, len);System.out.println("下载进度:" + raf.length() * 100 / contentLength + "%, " + raf.length() + '/' + contentLength + " bytes");}}// 下载完成,重命名临时文件成正式文件targetFile.renameTo(new File(filePath));System.out.println("下载完成");} catch (Exception e) {e.printStackTrace();}}
}

2.3、运行结果

本地运行之后可自行打断重试,从而测试断点续传是否生效,以下是我这边断点续传的结果

图片做了裁剪拼接,左边是断点续传的起始截图,右边是程序结束截图,左右拼接得到的以下图片

image.png

3、RandomAccessFile

RandomAccessFileJava I/O库中的一个特殊类,不属于InputStream或OutputStream的子类,它支持对文件的随机访问读写,也就是说,可以访问文件的任意位置。正因为这一特性,在代码中采用该类进行实现文件写入,通过seek()方法移动文件指针,而后将续传的内容拼接写入文件中。
该类的构造函数中可传入不同的运行模式:

  • r代表以只读方式打开指定文件 。
  • rw以读写方式打开指定文件 。
  • rws读写方式打开,并对内容或元数据都同步写入底层存储设备 。
  • rwd读写方式打开,对文件内容的更新同步更新至底层存储设备 。

4、思维拓展

通过前面我们知道Range请求头可用于断点续传实现,这得益于Range请求头支持指定请求范围的特性,那么这个特性我们还能不能用于其它业务场景的视线呢?答案肯定是可以的。
比如现在有100w个静态资源文件链接需要检测该文件是否可用,即需要确定这些文件链接的可访问性,比较清晰明了的方法就是都请求一遍,若正常请求通了,那便是可访问的。
但这有一个问题,这些都是静态资源文件的链接,即他们的下载地址,那么当我们全部请求一遍之后,那就代表着我们对这100w个文件都进行了下载,这无疑是对服务器的带宽、内存和CPU等资源是一个巨大的开销。
这种情况下我们就可以使用Range请求头进行实现这种检测的需求,每次请求之前携带上Range请求头,内容为Range: bytes=0-1,这代表每一个链接我都只尝试获取1字节的内容,从而大大减少了网络流量的流通和性能的提高。

参考资料

  1. HTTP | MDN
  2. 图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理 - 承香墨影 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/29398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

复位电路。

复位电路一般用到 电容电阻。 有的 是低电平复位&#xff0c;有的是高电平复位。 这里的电容的作用&#xff0c;上电自动复位。 参考&#xff1a; 此时 电容还能起到 抗干扰的作用。吸收 噪声。 电阻的作用&#xff0c;不是很理解&#xff0c;感觉是 在 上拉没有形成之前&am…

ninja构建笔记

在ninja构建系统中&#xff0c;“rule”和“build”这两个词通常关联但扮演着不同的角色。下面我会详细解释它们的区别&#xff1a; ### Rule (规则) 在构建系统中&#xff0c;“rule”通常指的是构建系统用来指导如何构建文件或者目标的指令集。它定义了一组转换输入到输出的…

数据库-单表查询-基本查询

查询数据表中指定字段的内容&#xff1a; SELECT 字段名 FROM 表名; 同时查询多个字段下的内容: SELECT 字段名1, 字段名2 FROM 表名; 查询数据表中的所有内容: SELECT * FROM 表名; 带IN关键字的查询: SELECT 字段名 FROM 表名 WHERE 字段名 IN (n1,n2,n3,...); IN关键字…

lora 继续训练

lora 继续训练 一般正常lora训练完模型后我们只保存其lora参数&#xff0c;然后与base模型进行合并。记录一下训练完保存lora后&#xff0c;继续再对lora训练的步骤。 base_model AutoModelForCausalLM.from_pretrained(base_model_id) base_model.enable_input_require_gra…

Multi-Head RAG:多头注意力的激活层作为嵌入进行文档检索

现有的RAG解决方案可能因为最相关的文档的嵌入可能在嵌入空间中相距很远&#xff0c;这样会导致检索过程变得复杂并且无效。为了解决这个问题&#xff0c;论文引入了多头RAG (MRAG)&#xff0c;这是一种利用Transformer的多头注意层的激活而不是解码器层作为获取多方面文档的新…

@Transactional实现原理及失效的三种情况

Transactional实现原理 Transactional是spring中定义的事务注解&#xff0c;在方法或类上加该注解开启事务。主要是通过反射获取当前对象的注解信息&#xff0c;利用AOP&#xff08;代理模式&#xff09;对编程式事务进行封装实现 // // Source code recreated from a .class f…

Win11版本21H2怎么升级为23H2?升级详细步骤在此!

在Win11电脑操作中&#xff0c;用户目前使用的版本是21H2&#xff0c;现在想体验23H2版本的先进功能&#xff0c;但不知道要怎么操作才能将系统版本升级为23H2&#xff1f;接下来小编给大家介绍详细的升级方法步骤&#xff0c;助力大家轻松完成系统版本升级操作。 方法一&#…

PostgreSQL源码分析——CHECKPOINT

checkpoint源码分析 因为检查点checkpoint相关的代码不是一篇文章就能分析完的&#xff0c;所以&#xff0c;相关的代码与逻辑可能会不连续&#xff0c;需要后续结合上下文理解。这里只列出了其中一部分。 其核心代码在&#xff1a;src/backend/postmaster/checkpointer.c以及…

05眼动识别软件详情2波形优化-滤波

对应视频链接点击直达 01项目点击下载&#xff0c;可直接运行&#xff08;含数据库&#xff09; 05眼动识别软件详情2 对应视频链接点击直达期望的数据展示数据波形对比如何实现几种常用滤波介绍维纳滤波巴特沃斯滤波器中值滤波排序滤波 推荐 结语其他以下是废话 原始数据的波…

CentOS 7.9上创建的JBOD阵列恢复(二)

系列文章目录 CentOS 7.9上创建JBOD&#xff08;一&#xff09; CentOS 7.9检测硬盘坏区、实物定位&#xff08;三&#xff09; 文章目录 系列文章目录前言一、用命令查看是否认到盘二、直接组JBOD三、挂载到新目录四、查看原数据总结 前言 在CentOS 7.9上创建了一个软阵列JB…

代码随想录算法训练营第四十二天| 1049. 最后一块石头的重量 II、 494. 目标和

LeetCode 1049. 最后一块石头的重量 II 题目链接&#xff1a;https://leetcode.cn/problems/last-stone-weight-ii/description/ 文章链接&#xff1a;https://programmercarl.com/1049.%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8FII…

SQL触发器基本结构

1. 修改分隔符的符号 delimiter $$ $$或者//都可以 2.创建触发器的名称 create trigger 函数名称 3. 什么样的操作会触发&#xff0c;操作的那个表 after&#xff1a;....之后触发 befor&#xff1a;....之前触发 insert&#xff1a;插入被触发 update&#xff1a;修改被触发 …

Kibana 一步步可视化实战构建步骤全集

今早读者朋友圈提及&#xff1a;《一本书讲透Elasticsearch》适合入门级体系化阅读&#xff0c;书中的3个项目实战&#xff0c;很适合实操&#xff0c;但是在建立Kibana可视化的过程中&#xff0c;书中没有明确构建步骤&#xff0c;在实操中会有缺憾。 我解释一下&#xff1a; …

CPU与GPU的原理不同

CPU&#xff08;中央处理器&#xff09;和GPU&#xff08;图形处理器&#xff09;在设计原理上有很大的不同。CPU是通用的计算核心&#xff0c;擅长处理复杂的控制流和数据结构&#xff0c;而GPU则是为了并行处理大量相似的计算任务而设计的。二者是计算机系统中两种不同类型的…

AI实战 | 使用元器打造浪漫仪式小管家

浪漫仪式小管家 以前我们曾经打造过学习助手和待办助手,但这一次,我们决定创造一个与众不同的智能体,而浪漫将成为我们的主题。我们选择浪漫作为主题,是因为我们感到在之前的打造过程中缺乏了一些仪式感,无法给对方带来真正的惊喜。因此,这一次我们计划慢慢调试,将它发…

vim 显示行号

在 Vim 中&#xff0c;你可以通过几种不同的方式来显示行号。以下是两种常用的方法&#xff1a; 临时显示行号&#xff1a; 当你打开 Vim 并想要临时查看文件的行号时&#xff0c;你可以使用 :set number 命令。这个命令会在当前 Vim 会话中显示行号。如果你想要关闭行号显示&a…

Python xml.dom.minidom 读取XML元素

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 什么是 XML&#xff1f; XML&#xff08;可扩展标记语言&#xff09;是一种用于描述数据结构和交换数据的标记语言。它被广泛用于 Web 应用程序中&#xff0c;用于存储和传输数据。XML 具有自描述性&#xff0c;因此…

GitHub|GitLab它们的区别是什么?

GitLab 和 GitHub 都是基于 Git 版本控制系统提供服务的平台&#xff0c;但它们是两个不同的产品&#xff0c;各自有着不同的特点和目标用户群。下面是对它们的基本解释和一些关键差异&#xff1a; GitHub 概述&#xff1a;GitHub 是全球最大的开源代码托管平台&#xff0c;由…

yocto根文件系统如何配置静态IP地址

在Yocto根文件系统中配置静态IP地址&#xff0c;你可以参考以下步骤。请注意&#xff0c;这些步骤可能会因Yocto版本和具体硬件平台的不同而略有差异。 1. 获取网络配置信息 首先&#xff0c;你需要从网络运维方获取分配的IP地址、子网掩码、默认网关和DNS信息。 2. 确定配置文…

文生视频开源产品的一些调研(一)

笔者尝试AI视频生成的几个特点&#xff1a; 玄学prompt&#xff0c;每个视频的prompt可能也需要微调很多次&#xff0c;需要找到使用模型的最佳prompt词组合&#xff0c;不恰当的比喻&#xff0c;骑自行车&#xff0c;座位高度等都是人与车彼此熟悉玄学生成&#xff0c;因为需…