专栏首页技术那些事HTTP方式文件分片断点下载

HTTP方式文件分片断点下载

前言

在进行大文件或网络带宽不是很好的情况下,分片断点下载就会显得很有必要,目前各大下载工具,如:迅雷等,都是很好的支持分片断点下载功能的。本文就通过http方式进行文件分片断点下载,进行实战说明。


HTTP之Range

在开始之前有必要了解一下相关概念及原理,即:HTTP之Range,才能更好的理解分片断点下载的原理。

什么是Range

Range是一个HTTP请求头,告知服务器要返回文件的哪一部分,即:哪个区间范围(字节)的数据,在 Range 中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略 Range 头,从而返回整个文件,状态码用 200

因为有了HTTP中Range请求头的存在,分片断点下载,便简单了许多。

当你正在看大片时,网络断了,你需要继续看的时候,文件服务器不支持断点的话,则你需要重新等待下载这个大片,才能继续观看。而Range支持的话,客户端就会记录了之前已经看过的视频文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个视频文件发送回客户端,以此节省网络带宽。

Range规范

Range: <unit>=<range-start>- Range: <unit>=<range-start>-<range-end> Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end> Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

<unit>:范围所采用的单位,通常是字节(bytes)

<range-start>:一个整数,表示在特定单位下,范围的起始值

<range-end>:一个整数,表示在特定单位下,范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束。

Range: bytes=1024-2048



分片断点下载之实现

以Java Spring Boot的方式来实现,核心代码如下:

  • serivce层
package com.xcbeyond.common.file.chunk.service.impl;
import com.xcbeyond.common.file.chunk.service.FileService;import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedOutputStream;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;
/** * 文件分片操作Service * @Auther: xcbeyond * @Date: 2019/5/9 23:02 */@Servicepublic class FileServiceImpl implements FileService {
    /**     * 文件分片下载     * @param range http请求头Range,用于表示请求指定部分的内容。     *              格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容     * @param request     * @param response     */    public void fileChunkDownload(String range, HttpServletRequest request, HttpServletResponse response) {        //要下载的文件,此处以项目pom.xml文件举例说明。实际项目请根据实际业务场景获取        File file = new File(System.getProperty("user.dir") + "\\pom.xml");
        //开始下载位置        long startByte = 0;        //结束下载位置        long endByte = file.length() - 1;
        //有range的话        if (range != null && range.contains("bytes=") && range.contains("-")) {            range = range.substring(range.lastIndexOf("=") + 1).trim();            String ranges[] = range.split("-");            try {                //根据range解析下载分片的位置区间                if (ranges.length == 1) {                    //情况1,如:bytes=-1024  从开始字节到第1024个字节的数据                    if (range.startsWith("-")) {                        endByte = Long.parseLong(ranges[0]);                    }                    //情况2,如:bytes=1024-  第1024个字节到最后字节的数据                    else if (range.endsWith("-")) {                        startByte = Long.parseLong(ranges[0]);                    }                }                //情况3,如:bytes=1024-2048  第1024个字节到2048个字节的数据                else if (ranges.length == 2) {                    startByte = Long.parseLong(ranges[0]);                    endByte = Long.parseLong(ranges[1]);                }
            } catch (NumberFormatException e) {                startByte = 0;                endByte = file.length() - 1;            }        }
        //要下载的长度        long contentLength = endByte - startByte + 1;        //文件名        String fileName = file.getName();        //文件类型        String contentType = request.getServletContext().getMimeType(fileName);
        //响应头设置        //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Ranges        response.setHeader("Accept-Ranges", "bytes");        //Content-Type 表示资源类型,如:文件类型        response.setHeader("Content-Type", contentType);        //Content-Disposition 表示响应内容以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。        // 这里文件名换成下载后你想要的文件名,inline表示内联的形式,即:浏览器直接下载        response.setHeader("Content-Disposition", "inline;filename=pom.xml");        //Content-Length 表示资源内容长度,即:文件大小        response.setHeader("Content-Length", String.valueOf(contentLength));        //Content-Range 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());
        response.setStatus(response.SC_OK);        response.setContentType(contentType);
        BufferedOutputStream outputStream = null;        RandomAccessFile randomAccessFile = null;        //已传送数据大小        long transmitted = 0;        try {            randomAccessFile = new RandomAccessFile(file, "r");            outputStream = new BufferedOutputStream(response.getOutputStream());            byte[] buff = new byte[2048];            int len = 0;            randomAccessFile.seek(startByte);            //判断是否到了最后不足2048(buff的length)个byte            while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {                outputStream.write(buff, 0, len);                transmitted += len;            }            //处理不足buff.length部分            if (transmitted < contentLength) {                len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));                outputStream.write(buff, 0, len);                transmitted += len;            }
            outputStream.flush();            response.flushBuffer();            randomAccessFile.close();
        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (randomAccessFile != null) {                    randomAccessFile.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }    }}
  • controller层
package com.xcbeyond.common.file.chunk.controller;
import com.xcbeyond.common.file.chunk.service.FileService;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
/** * 文件分片操作Controller * @Auther: xcbeyond * @Date: 2019/5/9 22:56 */@RestControllerpublic class FileController {    @Resource    private FileService fileService;
    /**     * 文件分片下载     * @param range http请求头Range,用于表示请求指定部分的内容。     *              格式为:Range: bytes=start-end  [start,end]表示,即是包含请求头的start及end字节的内容     * @param request   http请求     * @param response  http响应     */    @RequestMapping(value = "/file/chunk/download", method = RequestMethod.GET)    public void fileChunkDownload(@RequestHeader(value = "Range") String range,                                  HttpServletRequest request, HttpServletResponse response) {        fileService.fileChunkDownload(range,request,response);    }}

通过postman进行测试验证,启动Spring Boot后,如:下载文件前1024个字节的数据(Range:bytes=0-1023),如下:

注:此处 实现中没有提供客户端,客户端可循环调用本例中下载接口,每次调用指定实际的下载偏移区间range。

请注意响应头Accept-Ranges、Content-Range

  • Accept-Ranges: 表示响应标识支持范围请求,字段的具体值用于定义范围请求的单位,如:bytes。当发现Accept-Range 头时,可以尝试继续之前中断的下载,而不是重新开始。
  • Content-Range: 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小],如:bytes 0-1023/2185

源码:https://github.com/xcbeyond/common-utils/tree/master/src/main/java/com/xcbeyond/common/file/chunk

(如果你觉得不错,不妨留下脚步,在GitHub上给个Star)

参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Range

本文分享自微信公众号 - 程序猿技术大咖(cxyjsdk),作者:xcbey0nd

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解线程池底层原理

    如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

    xcbeyond
  • 随时随地编程,GitHub App 终于来了

    当然了,作为一个“交友平台”,更重要的是方便了大家随时随地交流,虽然GitHub表示是为了让大家在手机上也方便办公,并且和自己的团队保持联系等等。

    xcbeyond
  • Java 工程师居家必备的 Intellij IDEA Top10 插件

    支持lombok的各种注解,从此不用写getter setter这些 可以把注解还原为原本的java代码 非常方便

    xcbeyond
  • ceph集群警告和错误类型

    Lucien168
  • 基于Apify+node+react/vue搭建一个有点意思的爬虫平台

    熟悉我的朋友可能会知道,我一向是不写热点的。为什么不写呢?是因为我不关注热点吗?其实也不是。有些事件我还是很关注的,也确实有不少想法和观点。但我一直奉行一个原则...

    徐小夕
  • 一个纯JS脚本的文档敲诈者剖析(附解密工具)

    0x00 概述 近日,腾讯反病毒实验室拦截到一个名为RAA的敲诈者木马,其所有的功能均在JS脚本里完成。这有别于过往敲诈者仅把JS脚本当作一个下载器,去下载和执...

    FB客服
  • 用友战略入股销售易的机率有多大?

    继金蝶战略投资纷享销客以来,很多人都在问销售易何时也能找到他的归属,有人说腾讯投资销售易就是终局,但显然更懂To C的腾讯并不了解销售易,而销售易自从被腾讯投资...

    人称T客
  • 入门编程,一定要从C语言开始吗?

    很多小伙伴在入门编程时,都是从咱们老九学堂的C语言课程开始的,但最近有的小伙伴问我,学编程一定要从C语言开始吗?直接学习JAVA可以吗?

    老九君
  • Java程序员月薪达到三万,需要技术水平达到什么程度?

    非著名程序员
  • Java程序员月薪达到三万,需要技术水平达到什么程度?(文末送书)

    最近跟朋友在一起聚会的时候,提了一个问题,说Java程序员如何能月薪达到三万,技术水平需要达到什么程度?人回答说这只能是大企业或者互联网企业工程师才能拿到。也许...

    芋道源码

扫码关注云+社区

领取腾讯云代金券