http协议中可能会遇到:请求取消或数据传输中断,这时客户端已经收到了部分数据,后面再请求时最好能请求剩余部分(断点续传);或者,对于某个较大的文件,能够支持客户端多线程分片下载...

以上在我们平时应用中已经有很多产品支持了,其中原理就和http协议中的Range有关。

Range是在 HTTP/1.1里新增的一个请求头字段域,rfc文档:​​RFC 7233: Hypertext Transfer Protocol (HTTP/1.1): Range Requests​​ HTTP协议博大精深,设计有很多巧妙的地方,Range也许就是一处吧。

1、Range协议:

1)检查服务端是否支持Range:

客户端发起请求,服务端收到后在response的header中如果有Accept-Ranges:bytes ,表示支持Range。

比如,我们通过curl -I发起一个请求,查看响应头信息:(curl -I 表示Show document info only)

$ curl -I http://127.0.0.1/rt1/2.mp4
HTTP/1.1 200 OK
Server: openresty/1.19.9.1
Date: Thu, 12 Jan 2023 08:04:32 GMT
Content-Type: video/mp4
Content-Length: 505199705
Last-Modified: Fri, 08 Jul 2022 07:09:25 GMT
Connection: keep-alive
ETag: "62c7d825-1e1cbc59"
Accept-Ranges: bytes

上面Accept-Ranges: bytes 表示界定范围的单位是 bytes;Content-Length 表示文件大小。如果服务端响应header内容Accept-Ranges:none 表示不支持!

2)Range协议格式:

客户端在header中添加Range信息,表示发起一次范围请求,格式如下:

Range:unit=first byte pos-[last byte pos]

例如: 单位(如bytes)= 开始字节位置-结束字节位置。

服务端响应在header中添加Range信息:

Content-Range: unit first byte pos-[last byte pos]/[entity length]
例如:Content-Range:字节 开始字节位置-结束字节位置/文件大小。

注:first byte pos从0开始!
看个例子:假设有个文件大小为 5000 个 byte,现要开启多线程下载(比如4个),每个线程负责下载一个部分,那么我们可以划分为:

Range: bytes=0-1199 头1200个字节
Range: bytes=1200-2399 第二个1200字节
Range: bytes=2400-3599 第三个1200字节
Range: bytes=3600-5000 最后的1400字节

服务器给出响应:
// 第1个响应
Content-Length:1200
Content-Range:bytes 0-1199/5000
// 第2个响应
Content-Length:1200
Content-Range:bytes 1200-2399/5000
// 第3个响应
Content-Length:1200
Content-Range:bytes 2400-3599/5000
// 第4个响应
Content-Length:1400
Content-Range:bytes 3600-5000/5000

Content-Length表示对应范围的大小,Content-Range返回和请求对应的范围。

3)Range请求的响应码:

Server通过请求头中的Range: bytes=0-xxx来判断是否是做 Range 请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,表示Partial Content,并设置Content-Range。如果无效,则返回416状态码,表明Request Range Not Satisfiable。如果请求头中不带 Range,那么 Server则正常响应,也不会设置 Content-Range 等。

  • 206:在请求成功的情况下,服务器会返回 206 Partial Content 状态码。
  • 416:在请求的范围越界的情况下(范围值超过了资源的大小),服务器会返回 416 Requested Range Not Satisfiable (请求的范围无法满足)状态码。
  • 200:服务端不支持Range请求的情况下,服务器会返回 200 OK 状态码。

4)与分块传输编码的对比

Transfer-Encoding 首部允许分块编码,这在数据量很大,并且在请求未能完全处理完成之前无法知晓响应的体积大小的情况下非常有用。服务器会直接把数据发送给客户端而无需进行缓冲或确定响应的精确大小——后者会增加延迟。范围请求与分块传输是兼容的,可以单独或搭配使用。

2、从服务端请求特定范围的数据

1)单一范围:

客户端请求时,可以在header中通过Range表示请求的范围,这时如果服务端支持Range,则会返回206状态码,并返回指定范围的数据,并在header中添加Content-Range;否则不支持,则返回200。

例如:(curl -i 表示打印响应头)

$ curl -i -H "Range: bytes=0-102300"   http://127.0.0.1/f1.txt
HTTP/1.1 206 Partial Content
Server: openresty/1.19.9.1
Date: Thu, 12 Jan 2023 09:34:49 GMT
Content-Type: text/plain
Content-Length: 10
Last-Modified: Thu, 12 Jan 2023 09:34:44 GMT
Connection: keep-alive
ETag: "63bfd434-a"
Content-Range: bytes 0-9/10

123456789

2)多范围:

$ curl -i -H "Range: bytes=0-1,4-5"   http://127.0.0.1/f1.txt
HTTP/1.1 206 Partial Content
Server: openresty/1.19.9.1
Date: Thu, 12 Jan 2023 09:36:21 GMT
Content-Type: multipart/byteranges; boundary=00000000000000000001
Content-Length: 198
Last-Modified: Thu, 12 Jan 2023 09:34:44 GMT
Connection: keep-alive
ETag: "63bfd434-a"


--00000000000000000001
Content-Type: text/plain
Content-Range: bytes 0-1/10

12
--00000000000000000001
Content-Type: text/plain
Content-Range: bytes 4-5/10

56
--00000000000000000001--

Range 头部也支持一次请求文档的多个部分。请求范围用一个逗号分隔开。

服务器返回 206 Partial Content 状态码和 Content-Type:multipart/byteranges; boundary=00000000000000000001 头部,Content-Type:multipart/byteranges 表示这个响应有多个 byterange。每一部分 byterange 都有他自己的 Content-type 头部和 Content-Range,并且使用 boundary 参数对 body 进行划分。

3)条件范围:

当(中断之后)重新开始请求更多资源片段的时候,必须确保自从上一个片段被接收之后该资源没有进行过修改。

The ​​If-Range​​​ 请求首部可以用来生成条件式范围请求:假如条件满足的话,条件请求就会生效,服务器会返回状态码为 ​​206​​​ Partial 的响应,以及相应的消息主体。假如条件未能得到满足,那么就会返回状态码为 ​​200​​​ OK 的响应,同时返回整个资源。该首部可以与 ​​Last-Modified​​​ 验证器或者 ​​ETag​​ 一起使用,但是二者不能同时使用。

参考:

​HTTP 请求范围 - HTTP | MDN​

​聊一聊HTTP的Range, Content-Range | HappyCoding:)​

3、示例:nginx提供视频下载服务

http的Range可以提供范围请求,最重要的应用就是视频播放。

1)nginx配置:

使用openresty的docker景象启动,配置如下

server {
listen 80;
server_name localhost;

location = /f1.txt {
root /data/;
}
location = /rt1/2.mp4 {
root /data/;
}

创建如下文件:/data/rt1/2.mp4 /data/f1.txt

说明:nginx本身支持了Range,我看nginx也提供了ngx_http_slice_module模块(--with-http_slice_module参数),支持Range回源。

2)浏览器访问:

配置好nginx后,通过浏览器访问:​​http://127.0.0.1/rt1/2.mp4​​ 打开chrome的控制面板,切换到network tab页

http协议之Range_HTTP

可以看到有多个请求,第一个请求的状态码是200,剩余请求的状态码都是206(partial content-部分内容)。而且chrome非常贴心地用内置的视频解码工具来播放MP4文件,还有暂停按钮。(如果开始播放后不暂停,nginx会不断地给chrome吐数据,这个过程不会中断,暂停按钮会中断此次通信,断开connection,此时nginx才会写入access log)。

参考:​​【nginx】大文件下载 - awildfish