HTTP学习笔记(三)

此篇笔记主要为HTTP传输大文件的方法

数据压缩

  • Accept-Ecoding:(请求字段)浏览器可接受的压缩格式。
  • Content-Encoding:(通用字段)通常为服务端采用的压缩格式。

缺点:

gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,再用 gzip 处理也不会变小(甚至还有可能会增大一点),所以它就失效了。

不过数据压缩在处理文本的时候效果还是很好的,所以各大网站的服务器都会使用这个手段作为“保底”。例如,在 Nginx 里就会使用“gzip on”指令,启用对“text/html”的压缩。

分块传输

如果大文件整体不能变小,那就把它“拆开”,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。

这样浏览器和服务器都不用在内存里保存文件的全部,每次只收发一小部分,网络也不会被大文件长时间占用,内存、带宽等资源也就节省下来了。

  • Transfer-Encoding: (响应头字段)可取值为chunked,表示报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
    • 分块传输也可以用于“流式数据”,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段“Content-Length”里给出确切的长度,所以也只能用 chunked 方式分块发送。
    • “Transfer-Encoding: chunked”和“Content-Length”这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)

注意:分块传输可以流式收发数据,节约内存和带宽,使用响应头字段“Transfer-Encoding: chunked”来表示,分块的格式是 16 进制长度头 + 数据块;

分块格式

说明:

每个分块包含两个部分,长度头和数据块;

长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;

数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;

最后用一个长度为 0 的块表示结束,即“0\r\n\r\n”。

范围请求

应用场景:观看电视剧跳过片头片尾、快进、拖动进度条等。

  • Accept-Ranges:(响应字段)取值为bytes,由服务器告诉浏览器,服务器支持范围请求。
    • 如果不支持范围请求,可以不添加该字段或者取值为none
  • Range:(请求字段)格式:”bytes=x-y“,其中,xy表示范围。x、y 表示的是“偏移量”,范围必须从 0 计数,例如前 10 个字节表示为“0-9”,第二个 10 字节表示为“10-19”,而“0-10”实际上是前 11 个字节。
    • Range 的格式也很灵活,起点 x 和终点 y 可以省略,能够很方便地表示正数或者倒数的范围。假设文件是 100 个字节,那么:
    • “0-”表示从文档起点到文档终点,相当于“0-99”,即整个文件;
    • “10-”是从第 10 个字节开始到文档末尾,相当于“10-99”;
    • “-1”是文档的最后一个字节,相当于“99-99”;
    • “-10”是从文档末尾倒数 10 个字节,相当于“90-99”。

服务器收到 Range 字段后,需要做四件事:

  1. 第一,它必须检查范围是否合法,比如文件只有 100 个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码 416,意思是“你的范围请求有误,我无法处理,请再检查一下”。
  2. 第二,如果范围正确,服务器就可以根据 Range 头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content”,和 200 的意思差不多,但表示 body 只是原数据的一部分。
  3. 第三,服务器要添加一个响应头字段 Content-Range,告诉片段的实际偏移量和资源的总大小,格式是“bytes x-y/length”,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。
  4. 最后剩下的就是发送数据了,直接把片段用 TCP 发给客户端,一个范围请求就算是处理完了。
  • Content-Range:(响应字段)由服务器告知客户端资源的偏移量和总大小。格式是“bytes x-y/length

不仅看视频的拖拽进度需要范围请求,常用的下载工具里的多段下载、断点续传也是基于它实现的,要点是:

先发个HEAD,看服务器是否支持范围请求,同时获取文件的大小;

开 N 个线程,每个线程使用 Range 字段划分出各自负责下载的片段,发请求传输数据;

下载意外中断也不怕,不必重头再来一遍,只要根据上次的下载记录,用 Range 请求剩下的那一部分就可以了。

注意:

范围请求可以只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字段“Range”和响应头字段“Content-Range”,响应状态码必须是 206

多段数据

范围请求一次只获取一个片段,其实它还支持在 Range 头里使用多个“x-y”,一次性获取多个片段数据。

这种情况需要使用一种特殊的 MIME 类型(响应字段):“Content-Type: multipart/byteranges”,表示报文的 body 是由多段字节序列组成的

并且还要用一个参数“boundary=xxx”给出段之间的分隔标记。

例如:请求范围在0-9和20-29的数据

客户端:

1
2
3
GET /testUrl HTTP/1.1
Host: xxxxxx
Range: bytes=0-9, 20-29

服务端返回(部分响应头):

1
2
3
4
Content-Type: multipart/byteranges; boundary=00000000001
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
分段请求格式

每一个分段必须以“- -boundary”开始(前面加两个“-”)

之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据

最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束。

这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块

其他知识补充

  • gzip的压缩率通常能够超过60%,而br算法是专为HTML设计的,压缩效率和性能比 gzip还要好,能够再提高20%的压缩密度。
    • gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,再用 gzip 处理也不会变小
  • Nginx的“gzip on”指令很智能,只会压缩文本数据,不会压缩图片、音频、视频。
  • Transfer-Encoding字段最常见的值是chunked,但也可以用gzip、deflate 等,表示传输时使用了压缩编码。
    • 注意这与Content -Encoding不同,Transfer - Encoding在传输后会被自动解码还原出原始数据,而Content- Encoding 则必须由应用自行解码。
  • 与范围请求有关的头字段还有If-Range(条件范围请求)。
  • 分块传输数据的时候,如果数据里含有回车换行(\r\n)是否会影响分块的处理呢?
    • 不会,因为分块前有数据长度说明。
    • http交给tcp进行传输的时候本来就会分块,那http分块的不同在于:在http层是看不到tcp的,它不知道下层协议是否会分块,下层是否分块对它来说没有意义,不关心。 在http里一个报文必须是完整交付,在处理大文件的时候就很不方便,所以就要分块,在http层面方便处理。 chunked主要是在http的层次来解决问题
  • 如果对一个被 gzip 的文件执行范围请求,比如“Range: bytes=10-19”,那么这个范围是应用于原文件还是压缩后的文件呢?
    • 如果原来的文件属于gzip,那么请求范围就是gzip原文件。(此时gzip文件就是原文件)
    • 如果是其他文件,但是被gzip压缩了,那么range的请求范围就是原文件。
    • 总之,range是针对原文件的。