HTTP 方式文件分片断点下载附JAVA实现

lingyuli
发布于 2020-9-8 10:29
1.5w浏览
0收藏

前言
在进行大文件或网络带宽不是很好的情况下,分片断点下载就会显得很有必要,目前各大下载工具,如:迅雷等,都是很好的支持分片断点下载功能的。本文就通过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
 */
@Service
public 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();
            }
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.

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
 */
@RestController
public 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);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

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

HTTP 方式文件分片断点下载附JAVA实现-鸿蒙开发者社区

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

 

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

HTTP 方式文件分片断点下载附JAVA实现-鸿蒙开发者社区

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

 

来源:InfoQ 

分类
标签
已于2020-9-8 10:29:25修改
收藏
回复
举报
回复
    相关推荐