HTTP传输大文件及断点续传的方式
1. 数据压缩
通常浏览器在发送请求时都会带着 “Accept-Encoding” 头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br 等,这样服务器就可以从中选择一种压缩算法,放进 “Content-Encoding” 响应头里用来标识这次传输使用的压缩算法,再把原数据压缩后发给浏览器。
优缺点:
数据压缩在处理文本的时候效果还是很好的,但是图片、音频视频等多媒体数据本身就已经是高度压缩的,再用压缩处理也不会变小,此种方式失效。
2. 分块传输
压缩是把大文件整体变小,但是如果大文件整体不能变小,那就把它“拆开”,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。
这样浏览器和服务器都不用在内存里保存文件的全部,每次只收发一小部分,网络也不会被大文件长时间占用,内存、带宽等资源也就节省下来了。
在响应报文里用头字段 “Transfer-Encoding: chunked” 表示报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)分批发送。
注意:“Transfer-Encoding: chunked” 和 “Content-Length”这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。
分块传输的编码规则:
- 每个分块含有两个部分,长度头和数据块
- 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示某一块数据的长度
- 数据块紧跟在长度头后,最后也用 CRLF 结尾
- 最后用一个长度为 0 的块表示结束,如下图:
浏览器在收到分块传输的数据后会自动重新组装出内容。
3. 范围请求及断点续传
分块传输可以解决大文件传输的问题,但是有这么一种情况,看电影的过程中快进到某一段,也就是获取大文件中的某一段数据,分块传输并没有这个功能,这就需要使用范围请求。
客户端在请求头里使用 ”Range: bytes=左范围-右范围“ 来表示只获取文件的一部分,但是范围请求不是 Web 服务器必备的功能,可以实现也可以不实现,所以服务器必须在响应头里使用字段 “Accept-Ranges: bytes” 明确告知客户端:“支持范围请求”(客户端发送HEAD请求查询)。
如果服务器不支持范围请求,那么服务器可以发送 “Accept-Ranges: none”,或者干脆不发送 “Accept-Ranges” 字段,这样客户端就认为服务器没有实现范围请求功能,只能收发整块文件。
服务器收到 Range 字段后,需要做四件事:
- 检查范围是否合法,如果范围越界,服务器就会返回状态码416,意思是“你的范围请求有误,我无法处理,请再检查一下”。
- 如果范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码 “206 Partial Content” ,和 200 的意思差不多,但表示 body 只是原数据的一部分。
- 服务器要添加一个响应头字段 Content-Range: bytes 左范围 - 右范围 / 资源的总大小,表示将要返回指定范围内的实体内容。
- 服务器向客户端发送数据。
有了范围请求之后,HTTP 处理大文件就更加轻松了,看视频时可以根据时间点计算出文件的 Range,不用下载整个文件,直接精确获取片段所在的数据内容。
多段下载、断点续传的应用:
-
客户端先发 HEAD,看服务器是否支持范围请求,同时获取文件的大小
-
多段下载:开 N 个线程,每个线程使用 Range 字段划分出各自负责下载的片段,发请求传输数据
-
断点续传:下载意外中断,不必重头再来一遍,只要根据上次的下载记录,用 Range 请求剩下的那一部分就可以了
4. 多段请求
HTTP协议还支持在 Range 头里使用多个“x-y”,一次性获取多个片段数据,如: ”Range: bytes=0-9, 20-29“ 。
这就需要服务端传递一种特殊的 MIME 类型:“multipart/byteranges”,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数 “boundary=xxx” 给出段之间的分隔标记,如下图的响应内容:
每一个分段必须以 “- -boundary” 开始,之后要用 “Content-Type” 和 “Content-Range” 标记这段数据的类型和所在范围,全部分段数据发送之后,最后用一个 “- -boundary- -” 表示所有的分段结束。