HTTP方式文件分片断点下载

news/2024/11/17 16:27:54/

前言

      在进行大文件或网络带宽不是很好的情况下,分片断点下载就会显得很有必要,目前各大下载工具,如:迅雷,都是很好的支持分片断点下载功能的。本文就通过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-Rangesresponse.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)个bytewhile ((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*/
@RestController
public class FileController {@Resourceprivate 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

 

欢迎微信扫码下面二维码,关注微信公众号【程序猿技术大咖】,进行更多交流学习!

 


http://www.ppmy.cn/news/744439.html

相关文章

安利一个超牛的资源网站,国庆看片就用它了

点击上方“GitHubPorn”&#xff0c;选择“星标”公众号 重磅干货&#xff0c;第一时间送达 来自公众号&#xff1a;扩展迷EXTFANS 作者&#xff1a;okay 国庆8天长假&#xff0c;你们有什么计划吗&#xff1f; 对于许多小伙伴来说&#xff0c;比起出门旅游&#xff0c;假期最爽…

什么是卫片?什么是卫片执法?一文了解卫片执法基础知识

一、什么是卫片? 通俗来说&#xff0c;就是卫星拍的照片&#xff0c;卫片如同“天眼”&#xff0c;通过卫片的“火眼金睛”&#xff0c;各种非法占地的情况一览无遗。 自然资源部通过卫星对全国范围内土地拍摄照片&#xff0c;并与上一轮拍摄的照片进行比对&#xff0c;只要…

IP分片与TCP分包

TCP/IP 协议栈中定义了MSS, 为的是提高网络的性能&#xff0c;因为如果让IP层分包的话&#xff0c;丢包后的重发没法控制&#xff0c;需要重传整个TCP包&#xff08;浪费了网络资源&#xff09;。 MSS就是这个协商的结果&#xff0c;不能人为修改&#xff0c;这个MSS的大小是MT…

【设计模式】第九章:外观模式(门面模式)详解及应用案例

系列文章 【设计模式】七大设计原则 【设计模式】第一章&#xff1a;单例模式 【设计模式】第二章&#xff1a;工厂模式 【设计模式】第三章&#xff1a;建造者模式 【设计模式】第四章&#xff1a;原型模式 【设计模式】第五章&#xff1a;适配器模式 【设计模式】第六章&…

闲谈IPv6-IPv6的分片(IPv6 Fragment)

从去年9月初以来&#xff0c;我把做实验写博客写代码的时间放在了晚上&#xff0c;但是现在&#xff0c;晚上要早睡觉&#xff0c;白天要被指使着干这干那&#xff0c;感觉还是周六的凌晨更是自己的时间。 本文最后&#xff0c;夹杂着一则关于 “皮鞋为什么比布鞋落后&#xf…

判断一个key在redis集群的哪一个节点(分片)

redis的集群模式下去需要确定一个key在哪个节点上的话&#xff0c;简单粗暴的就是一个个节点找过去。但是这样明显是费时费力的。 其实可以直接确定这个key在redis的哪个节点上&#xff0c;然后直接去这个节点找这个key就可以了. 1-先查看集群的几个节点的分片范围情况 ./re…

AI“看片儿”比人快,鉴黄师却说不靠谱?

本文转载自钛媒体 一直以来&#xff0c;“鉴黄师”被认为是一种神秘且高薪的职业&#xff0c;很多普通人都对这个职业充满了好奇&#xff0c;甚至有人想做个兼职&#xff0c;以为轻轻松松就能赚钱&#xff0c;但这个职业真的是“钱多事少”吗&#xff1f; 事实上&#xff0c;…

从Wireshark抓包来看IP分片

文件来自于前几天CyBRICS 2021中的lx100题目&#xff0c;因为做题时候被IP分片坑到了&#xff0c;发现自己对于网络这一块的知识掌握的并不好&#xff0c;所以写一篇文章来理一下。为了省事就直接用比赛的pcap文件做样例了&#xff1a;点击。 从Wireshark抓包来看IP分片 UDP/…