手把手教你 Java 文件断点下载
来源:juejin.cn/post/7026372482110079012
👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 赠书福利
全栈前后端分离博客项目 1.0 版本完结啦,2.0 正在更新中..., 演示链接:http://116.62.199.48/ ,全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了189小节,累计31w+字,讲解图:1308张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有850+小伙伴加入(早鸟价超低)
- 前言
- 生产实战
- 服务端 - 业务开发
前言
图片❝
互联网的连接速度慢且不稳定,有可能由于网络故障导致断开连接。
在客户端下载一个大对象时,因网络断开导致上传下载失败的概率就会变得不可忽视。
❞
客户端在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 Content
:HTTP Range
请求成功 -
416 Requested Range Not Satisfiable status
:HTTP Range
请求超出界限 -
200 OK
:不支持范围请求
小结如下:
-
HTTP
范围请求:需要HTTP/1.1
及之上支持,如果双端某一段低于此版本,则认为不支持。 - 通过响应头中的
Accept-Ranges
来确定是否支持范围请求。 - 通过在请求头中添加
Range
这个请求头,来指定请求的内容实体的字节范围。 - 在响应头中,通过
Content-Range
来标识当前返回的内容实体范围,并使用 Content-Length 来标识当前返回的内容实体范围长度。 - 在请求过程中,可以通过
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
为栗子:
- 对外支持
range
下载 - 底层存储:使用
ceph
-
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);
}
}
-
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]);
} else if (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);
}
}
以上内容,大家可以收藏起来,如果以后遇到这样的场景,分分钟搞定!
👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 赠书福利
全栈前后端分离博客项目 1.0 版本完结啦,2.0 正在更新中..., 演示链接:http://116.62.199.48/ ,全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了189小节,累计31w+字,讲解图:1308张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有850+小伙伴加入(早鸟价超低)
1. 我的私密学习小圈子~
2. 卷翻了,功能强大,企业级的微服务开发平台开源了....
最近面试BAT,整理一份面试资料 《Java面试BATJ通关手册》 ,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“ 在看 ”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下 “在看” ,加个 “星标” ,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦