http 断点续传
一、序
Hi,大家好,我是承香墨影!
HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文,而报文又是由报文头(Header)和实体组成。大多数 HTTP 协议的使用方式,都是依赖设置不同的 HTTP 请求/响应 的 Header 来实现的。
本系列《实用 HTTP》就抛开常规的 Header 讲解式的表述方式,从实际问题出发,来分析这些 HTTP 协议的使用方式,到底是为了解决什么问题?同时讲解它是如何设计的和它实现原理。
HTTP 协议是一种无状态的“松散协议”,它不会记录不同请求的状态,并且因为它本身包含了两端(客户端和服务端),根据请求和响应来区分,它大部分的内容都只是一个建议,其实双边是可以不遵守此建议的。
“这里写了建议零售价 2 元...”
“哦,不接受建议!”
文本是本系列的第五篇,前四篇传送门:
今天再来介绍一下 HTTP 的范围请求。范围请求主要是针对较大的文件的请求或者上传,可以仅操作它的某一段。
一个比较常见的场景,就是断点续传/下载,在网络情况不好的时候,可以在断开连接以后,仅继续获取部分内容。例如在网上下载软件,已经下载了 95% 了,此时网络断了,如果不支持范围请求,那就只有被迫重头开始下载。但是如果有范围请求的加持,就只需要下载最后 5% 的资源,避免重新下载。
另一个场景就是多线程下载,对大型文件,开启多个线程,每个线程下载其中的某一段,最后下载完成之后,在本地拼接成一个完整的文件,可以更有效的利用资源。
这算是两个比较常见的场景,接下来我们来看看范围请求的 HTTP 协议支持的技术细节。
二、HTTP 的范围请求
2.1 是否支持范围请求
HTTP 本身是一种无状态的“松散”协议,而在经历了很多版本的迭代之后,只在 HTTP/1.1(RFC2616) 之上,才支持范围请求。所以如果客户端或者服务端两端的某一端低于 HTTP/1.1,我们就不应该使用范围请求的功能。
而在 HTTP/1.1 中,很明确的声明了一个响应头部 Access-Ranges
来标记是否支持范围请求,它只有一个可选参数 bytes
。
例如这里给了一个 MP4 的响应头,可以看到它是有 Accept-Ranges:bytes
来标记的,有此标记标识当前资源支持范围请求。
2.2 使用范围请求
如果已经确定双端都支持范围请求,我们就可以在请求资源的时候使用它。
所有的文件最终都是存储在磁盘或者内存中的字节,对于待操作的文件可以将其以字节为单位分割。这样只需要 HTTP 支持请求该文件从 n 到 n+x 这个范围内的资源,就可以实现范围请求了。
HTTP/1.1 中定义了一个 Ranges 的请求头,来指定请求实体的范围。它的范围取值是在 0 - Content-Length
之间,使用 -
分割。。
例如已经下载了 1000 bytes 的资源内容,想接着继续下载之后的资源内容,只要在 HTTP 请求头部,增加 Ranges:bytes=1000-
就可以了。
Range 还有几种不同的方式来限定范围,可以根据需要灵活定制:
1. 500-1000:指定开始和结束的范围,一般用于多线程下载。
2. 500- :指定开始区间,一直传递到结束。这个就比较适用于断点续传、或者在线播放等等。
3. -500:无开始区间,只意思是需要最后 500 bytes 的内容实体。
4. 100-300,1000-3000:指定多个范围,这种方式使用的场景很少,了解一下就好了。
HTTP 协议是一种双边协商的协议,既然请求头部已经确定是使用 Ranges 了,还有响应头部中,也需要使用 Content-Ragne
这个响应头来标记响应的实体内容范围。
Content-Range
的格式也很清晰,首先标记它的单位是 bytes 然后标记当前传递的内容实体范围和总长度。
Content-Range: bytes 100-999/1000
在这个例子中,会传递 100 ~ 999 范围的内容实体,而该资源文件的总大小是 1000 bytes。并且此时的 HTTP 响应状态码为 206 Partial Content
。
HTTP 206 Partial Content 成功状态响应代码表示请求已成功,并且主体包含所请求的数据区间,该数据区间是在请求的
Range
首部指定的。有关 206 状态码的解释可以参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/206
所以一个正常的流程应该如下图所示:
注意这里的每个 HTTP 事务中的响应头里,都是会包含 Content-Length
的,只是它包含的是当前范围请求响应的内容实体长度,而非此资源完整的长度。
到这里基本上算是讲清楚 HTTP 范围请求的正确流程了,接下来看看一些特殊的情况。
2.3 资源变化
当我们在一些下载工具中,下载大尺寸资源的时候,偶尔中间暂停过再重新下载,可能会遇见它又重头开始下载的情况。
这看似是 HTTP 的范围请求失效了,但是实际上并不一定如此,很可能是因为请求的资源,在请求的这个过程中,发生了改变。
假如你下载的过程中,下载的源资源文件发生了变化,但是 URL 没有改变,此时文件长度可能已经变化了(这是非常容易发现的),极端情况下就算没有长度没有变化,你再继续下载,很可能最终下载完成之后,无法将下载的内容拼接成我们需要的文件。
如果我们需要从服务器上下载某个资源,一定要预防此资源可能发生的变动。在之前讲 HTTP 缓存的时候讲到,在 HTTP 协议中,可以通过 ETag 或者 Last-Modified 来标识当前资源是否变化。
- ETag:当前文件的一个验证令牌指纹,用于标识文件的唯一性。
- Last-Modified:标记当前文件最后被修改的时间。
在 HTTP 的范围请求中,也可以使用这两个字段来区分分段请求的资源,是否有修改过,只需要在请求头中,将它放在 If-Range
这个请求报文头中即可。If-Range
使用 ETag
或者 Last-Modified
两个参数任意一个,原样填入即可。
此时,如果两次操作的都是同一个资源文件,就会继续返回 206 状态码,开始后续的操作,反之则会返回 200 状态码,表示文件发生改变,要从头下载。
需要注意的是 If-Range
需要和 Range
配合起来使用,否则会被服务端忽略。
再额外提一点,如果客户端请求报文头中,对 Range 填入的范围错误,会返回 416 状态码。
HTTP 416 Range Not Satisfiable 错误状态码意味着服务器无法处理所请求的数据区间。最常见的情况是所请求的数据区间不在文件范围之内,也就是说,
Range
首部的值,虽然从语法上来说是没问题的,但是从语义上来说却没有意义。有关 416 状态码,可以参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/416
三、范围请求的例子
3.1 用 Chrome 播放一个适配
前面介绍的概念,很多技术点其实描述的都是某一个请求片段,接下来我们以一个实际的例子来说明范围请求的具体细节。
在这个例子中,我找了一个视频的播放地址,直接在 Chrome 中进行播放。正常播放之后,再随手拖动视频进度,之后无操作让其自动播放一段时间,来看看 HTTP 的事务报文。
简单描述一下情况,自然播放的时候,会首先想资源的 URL 发送请求,返回 200 的响应码,可以判断出当前资源支持 Accept-Ranges
,接下来会去使用 Range
发送范围请求,得到的响应码就是 206,并返回对应范围的实体内容。而在每次拖动进度的时候,都会去重新发送一个范围请求,依照拖动的进度来计算请求范围。此处不存在资源被修改的情况,所以不会出现重新请求下载的情况。
就不一个一个对 HTTP 事务截图了,大概抽象了一下流程,如下图所示:
可以看到,一次资源下载其实包含了很多次的请求过程,我们需要站在全局的角度来看到它。
四、范围请求小结
到这里我们就已经把 HTTP 范围请求的整个流程都说明清楚了。
再重新整理一下关键点:
1. HTTP 范围请求,需要 HTTP/1.1 及之上支持,如果双端某一段低于此版本,则认为不支持。
2. 通过响应头中的 Accept-Ranges
来确定是否支持范围请求。
3. 通过在请求头中添加 Range
这个请求头,来指定请求的内容实体的字节范围。
4. 在响应头中,通过 Content-Range
来标识当前返回的内容实体范围,并使用 Content-Length 来标识当前返回的内容实体范围长度。
5. 在请求过程中,可以通过 If-Range
来区分资源文件是否变动,它的值来自 ETag 或者 Last-Modifled。如果资源文件有改动,会重新走下载流程。
再配一张流程图,就更清晰了。
到此 HTTP 范围请求的所有关键技术点,就已经讲解清楚。范围请求被用在诸如:断点续传、多线程下载等场景下,大部分 CDN 上的资源都是支持范围请求的,具体你能在什么场景下应用,就看你的想象力了。
http 断点续传的更多相关文章
- HTML5实现文件断点续传
HTML5的FILE api,有一个slice方法,可以将BLOB对象进行分割.前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段 ...
- 总结iOS开发中的断点续传那些事儿
前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...
- (实例篇)PHP实现HTTP断点续传的方法
PHP实现HTTP断点续传的方法. <?php /** * PHP-HTTP断点续传实现 * @param string $path: 文件所在路径 * @param string $file: ...
- C# 文件下载之断点续传
注意,本文所说的断点续传特指 HTTP 协议中的断点续传.本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo. 工作原理 HTTP 协议中定义了一些请求/响应头,通过组合使用这些头信息.我 ...
- chunkupload文件上传断点续传组件(java)
chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. 从整体上讲,chunkupload会对文件进行切片处理,每个切片 ...
- wget 断点续传 & nginx文件服务器
nginx默认支持断点续传: 测试方法: wget -S http://httpd.apache.org/images/httpd_logo_wide_new.png 2>&1 | gr ...
- chunkupload 文件上传断点续传组件(java) - 正式发布
chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. chunkupload实现如下功能: · 实现断点续传 · ...
- ASP.NET WebAPi之断点续传下载(下)
前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...
- ASP.NET WebAPi之断点续传下载(中)
前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...
- ASP.NET WebAPi之断点续传下载(上)
前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...
随机推荐
- CentOS环境PHP安装memcache扩展
安装memcache yum install memcached 安装libmemcached库 yum install libmemcached PHP安装Memcache扩展 root@blog. ...
- spring中xml配置方式和注解annoation方式(包括@autowired和@resource)的区别
xml文件中配置itemSqlParameterSourceProvider是可以的: <bean id="billDbWriter" class="com.aa. ...
- iOS微信实现第三方登录的方法
这篇文章主要介绍了iOS微信第三方登录实现的全过程,一步一步告诉大家iOS微信实现第三方登录的方法,感兴趣的小伙伴们可以参考一下 一.接入微信第三方登录准备工作.移动应用微信登录是基于OAuth2 ...
- java.io.BufferedInputStream 源码分析
BufferedInputStream是一个带缓冲区的输入流,在读取字节数据时可以从底层流中一次性读取多个字节到缓冲区,而不必每次读取操作都调用底层流,从而提高系统性能. 先介绍几个关键属性 //默认 ...
- CPP_const&static
const 1. 定义本地常量,替换宏.#define LENGHTH 16static const int LENGHTH = 16;2. const出现在星号左边,表示被指物是常量:const出现 ...
- Java编程的逻辑 (48) - 剖析ArrayDeque
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Flutter 1.0 正式版: Google 的便携 UI 工具包
Flutter 1.0 正式版: Google 的便携 UI 工具包 文 / Tim Sneath,Google Dart & Flutter 产品组产品经理 Flutter 是 Google ...
- 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰
大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...
- Sword websocket分析二
//websocket发送数据 int send(uint8_t* message, uint64_t message_size) { //掩码 ] = { 0x12, 0x34, 0x56, 0x7 ...
- hashMap 临界值初步理解
import java.util.*; public class Bs { //Integer.highestOneBit((number - 1) << 1)分解 public stat ...