首页 > 其他分享 >Spring Boot 实现文件断点下载,实战来了!

Spring Boot 实现文件断点下载,实战来了!

时间:2023-07-26 11:22:41浏览次数:39  
标签:HTTP 字节 Spring bytes range Content Range Boot 断点

来源:juejin.cn/post/7026372482110079012

前言

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

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

客户端在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 为栗子,Spring Boot 基础就不介绍了,推荐看这个实战项目:

https://github.com/javastacks/spring-boot-best-practice

  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]);
                } 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);
    }
}

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

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

标签:HTTP,字节,Spring,bytes,range,Content,Range,Boot,断点
From: https://www.cnblogs.com/javastack/p/17581957.html

相关文章

  • spring启动流程 (6完结) springmvc启动流程
    SpringMVC的启动入口在SpringServletContainerInitializer类,它是ServletContainerInitializer实现类(Servlet3.0新特性)。在实现方法中使用WebApplicationInitializer创建ApplicationContext、创建注册DispatcherServlet、初始化ApplicationContext等。SpringMVC已经将大部分的启......
  • 你真正了解Spring的工作原理吗
     Spring  1.1什么是SpringIOC和DI?  ①控制反转(IOC):Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。②依赖注入(DI):Spring使用JavaBean对象的Set方法或者带参数的构造方法......
  • SpringBoot+Prometheus+Grafana实现系统可视化监控
    场景SpringBoot中集成Actuator实现监控系统运行状态:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124272494基于以上Actuator实现系统监控,还可采用如下方案。PrometheusPrometheus,是一个开源的系统监控和告警的工具包,其采用Pull方式采集时间序列的度量数据(也......
  • 我开源了团队内部基于SpringBoot Web快速开发的API脚手架v1.6.0更新
    什么是rest-api-spring-boot-starterrest-api-spring-boot-starter适用于SpringBootWebAPI快速构建让开发人员快速构建统一规范的业务RestFullAPI不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。动机每次WebAPI常用功能都需要重新写一遍。或者复制之前的项目代码......
  • springboot 解决高并发下的商品少卖多卖的问题
    1.商品秒杀-超卖在开发中,对于下面的代码,可能很熟悉:在Service里面加上@Transactional事务注解和Lock锁。控制层:Controller@ApiOperation(value="秒杀实现方式——Lock加锁")@PostMapping("/start/lock")public Result startLock(long skgId){    try {        ......
  • springboot整合junit
       ......
  • 多环境开发兼容问题(Maven与Springboot)
          ......
  • 简单的bootloader
    下面是一个简单的启动代码示例,用于展示一个最基本的引导加载程序(bootloader)的结构和功能:.global_start.section.text_start:#设置堆栈指针movsp,#0x10000#加载内核镜像到内存中的地址ldrr0,=0x8000ldrr1,=kernel_imageldrr2,=kernel_sizeldmiar1!,{r3}str......
  • spring-boot-yaml的用法
    1.yaml简洁以数据为核心·基本语法大小写敏感数值前必须要有空格,作为分割符·数据格式对象数组(使用“-”表示数组每个元素)常量·参数引用$server:port:2023#数据的定义name:lisi#对象的定义person:name:${lisi}age:12......
  • 前端之Bootstarp框架
    Bootstarp简介Bootstrap是一个用于快速开发Web应用程序和网站的前端框架。Bootstrap是前端开发中比较受欢迎的框架,简洁且灵活。它基于HTML、CSS和JavaScript,HTML定义页面元素,CSS定义页面布局,而JavaScript负责页面元素的响应。Bootstrap将HTML、CSS和JavaScript封装成一个......