题图:by Charles Loyerhtml
Hi,你们好,我是承香墨影!小程序
HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文,而报文又是由报文头(Header)和实体组成。大多数 HTTP 协议的使用方式,都是依赖设置不一样的 HTTP 请求/响应 的 Header 来实现的。缓存
本系列《实用 HTTP》就抛开常规的 Header 讲解式的表述方式,从实际问题出发,来分析这些 HTTP 协议的使用方式,究竟是为了解决什么问题?同时讲解它是如何设计的和它实现原理。服务器
HTTP 协议是一种无状态的“松散协议”,它不会记录不一样请求的状态,而且由于它自己包含了两端(客户端和服务端),根据请求和响应来区分,它大部分的内容都只是一个建议,其实双边是能够不遵照此建议的。网络
“这里写了建议零售价 2 元...”多线程
“哦,不接受建议!”工具
文本是本系列的第五篇,前四篇传送门:布局
今天再来介绍一下 HTTP 的范围请求。范围请求主要是针对较大的文件的请求或者上传,能够仅操做它的某一段。学习
一个比较常见的场景,就是断点续传/下载,在网络状况很差的时候,能够在断开链接之后,仅继续获取部份内容。例如在网上下载软件,已经下载了 95% 了,此时网络断了,若是不支持范围请求,那就只有被迫重头开始下载。可是若是有范围请求的加持,就只须要下载最后 5% 的资源,避免从新下载。编码
另外一个场景就是多线程下载,对大型文件,开启多个线程,每一个线程下载其中的某一段,最后下载完成以后,在本地拼接成一个完整的文件,能够更有效的利用资源。
这算是两个比较常见的场景,接下来咱们来看看范围请求的 HTTP 协议支持的技术细节。
HTTP 自己是一种无状态的“松散”协议,而在经历了不少版本的迭代以后,只在 HTTP/1.1(RFC2616) 之上,才支持范围请求。因此若是客户端或者服务端两端的某一端低于 HTTP/1.1,咱们就不该该使用范围请求的功能。
而在 HTTP/1.1 中,很明确的声明了一个响应头部 Access-Ranges
来标记是否支持范围请求,它只有一个可选参数 bytes
。
例如这里给了一个 MP4 的响应头,能够看到它是有 Accept-Ranges:bytes
来标记的,有此标记标识当前资源支持范围请求。
若是已经肯定双端都支持范围请求,咱们就能够在请求资源的时候使用它。
全部的文件最终都是存储在磁盘或者内存中的字节,对于待操做的文件能够将其以字节为单位分割。这样只须要 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 范围请求的正确流程了,接下来看看一些特殊的状况。
当咱们在一些下载工具中,下载大尺寸资源的时候,偶尔中间暂停过再从新下载,可能会碰见它又重头开始下载的状况。
这看似是 HTTP 的范围请求失效了,可是实际上并不必定如此,极可能是由于请求的资源,在请求的这个过程当中,发生了改变。
假如你下载的过程当中,下载的源资源文件发生了变化,可是 URL 没有改变,此时文件长度可能已经变化了(这是很是容易发现的),极端状况下就算没有长度没有变化,你再继续下载,极可能最终下载完成以后,没法将下载的内容拼接成咱们须要的文件。
若是咱们须要从服务器上下载某个资源,必定要预防此资源可能发生的变更。在以前讲 HTTP 缓存的时候讲到,在 HTTP 协议中,能够经过 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
前面介绍的概念,不少技术点其实描述的都是某一个请求片断,接下来咱们以一个实际的例子来讲明范围请求的具体细节。
在这个例子中,我找了一个视频的播放地址,直接在 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 上的资源都是支持范围请求的,具体你能在什么场景下应用,就看你的想象力了。
还有什么更多的想法,欢迎留言讨论。
公众号后台回复成长『成长』,将会获得我准备的学习资料,也能回复『加群』,一块儿学习进步;你还能回复『提问』,向我发起提问。
推荐阅读:
Android P 适配经验 | 技术创业选择清单 | HTTP传输编码 | 什么正在消耗你? | HTTP 内容编码 | 图解 HTTP 缓存 | 聊聊 HTTP 的 Cookie | 辅助模式实战 | Accessibility 辅助模式 | 小程序 Flex 布局 | 好的 PR 让你更靠谱 | 密码管理之道