图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理

题图:by Charles Loyer

一、序

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 上的资源都是支持范围请求的,具体你能在什么场景下应用,就看你的想象力了。

还有什么更多的想法,欢迎留言讨论。


公众号后台回复成长『成长』,将会得到我准备的学习资料,也能回复『加群』,一起学习进步;你还能回复『提问』,向我发起提问。

推荐阅读:

Android P 适配经验 | 技术创业选择清单 | HTTP传输编码 | 什么正在消耗你? | HTTP 内容编码 | 图解 HTTP 缓存 | 聊聊 HTTP 的 Cookie | 辅助模式实战 | Accessibility 辅助模式 | 小程序 Flex 布局 | 好的 PR 让你更靠谱 | 密码管理之道

原文地址:https://www.cnblogs.com/plokmju/p/http_range.html

时间: 2024-10-07 07:35:37

图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理的相关文章

断点续传---多线程下载进阶(一)

打算总结7篇笔记,来学习下断点续传---多线程下载进阶 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sms.down" android:versionCode="1&quo

libcurl的封装,支持同步异步请求,支持多线程下载,支持https

最近在做一个项目,需要用到http get post等 需求分析需要做到同步和异步,异步请求的返回以可选的回调通知的方式进行. 本人以Linux为例,一步一步的来实现. 配置并且编译libcurl我以在Linux底下的交叉编译举例.libcurl源码下载: http://curl.haxx.se/download.html配置libcurl支持https和zlib压缩,必须需要openssl和zlib库openssl库源码下载: http://www.openssl.org/source/.下载

Android版多线程下载器核心代码分享

首先给大家分享多线程下载核心类: 1 package com.example.urltest; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.RandomAccessFile; 6 import java.net.HttpURLConnection; 7 import java.net.MalformedURLException; 8 import java.net.URL; 9 im

【Android-011】【多线程下载】

Android学习目录 项目源码下载 多线程下载 原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源 确定每条线程下载多少数据 发送http请求至下载地址 String path = "http://192.168.1.102:8080/editplus.exe"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) ur

Android 多线程下载,断点续传,线程池

你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader 效果图 这张效果图是同时开启三个下载任务,限制下载线程数量的效果图. 多线程下载原理 多线程下载的原理就是将下载任务分割成一个个小片段再将每个小片段分配给各个线程进行下载. 例如一个文件大小为100M,我们决定使用4个线程下载,那么每个线程下载的大小即为25M,每个线程的起始以及结束位置依次如下: 0: 0-25M 1: 25-50M 2: 50-75M 3

【幻化万千戏红尘】qianfengDay27-HttpURLConnection,OkHttpClient,,多线程下载且断点续传基础学习:

课程回顾: Servlet:java语言开发的运行在服务器上的开发步骤:1.创建Servlet类2.重写doGet或doPost方法3.运行在服务器 生命周期:1.初始化2.服务3.销毁 URL:统一资源定位符,网址openConnection 今日内容:Http协议:超文本传输协议常用方式:1.HttpURLConnection2.okHttp HttpURLConnection的使用步骤:1.创建URL对象---URL url=new URL("网址");2.获取连接对象--Htt

Android 多线程断点续传同时下载多个大文件

最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识.在这里解析一下自己写的demo,总结一下自己所学的知识.下图为demo的效果图,仿照一些应用下载商城在ListView中列出加载项,然后可以可以下载和停止. 1.概述 这里有几个比较重要的类DownloadManager.DownloadService.DownloadTask.ThreadDAOImpl.主要的下载流程如下. (1) DownloadManager 负责下载任务的调配,以及下载服务Dow

多线程断点续传及下载

断点下载 package com.example.downloaddemo; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import android.os.Bundle; import android.os.Handler; import android.os.

多线程下载文件(支持暂停、取消、断点续传)

多线程下载文件(支持暂停.取消.断点续传) 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可. 涉及的知识及问题 请求的数据如何分段 分段完成后如何下载和下载完成后如何组装到一起 暂停下载和继续下载的实现(wait().notifyAll().synchronized的使用) 取消下载和断点续传的实现 一.请求的数据如何分段 首先通过HttpURLConne