ASP.NET Core静态文件中间件[3]: 区间请求以提供部分内容
大部分针对物理文件的请求都希望获取整个文件的内容,区间请求则与之相反,它希望获取某个文件部分区间的内容。区间请求可以通过多次请求来获取某个较大文件的全部内容,并实现断点续传。如果同一个文件同时存放到多台服务器,就可以利用区间请求同时下载不同部分的内容。与条件请求一样,区间请求也作为标准定义在HTTP规范之中。
HTTP区间请求
如果希望通过一个GET请求获取目标资源的某个区间的内容,就需要将这个区间存放到一个名为Range的报头中。虽然HTTP规范允许指定多个区间,但是StaticFileMiddleware中间件只支持单一区间。分区所采用的计量单位,HTTP规范并未做强制的规定,但是StaticFileMiddleware中间件支持的单位为Byte,也就是说,它是以字节为单位对文件内容进行分区的。
Range报头携带的分区信息采用的格式为bytes={from}-{to}({from}和{to}分别表示区间开始与结束的位置),如bytes=1000-1999表示获取目标资源从1001到2000共计1000字节(第1个字节的位置为0)。如果{to}大于整个资源的长度,这样的区间依然被认为是有效的,它表示从{from}到资源的最后一个字节。如果区间被定义成bytes={from}-这种形式,同样表示区间从{from}到资源的最后一个字节。采用bytes=-{n}格式定义的区间则表示资源的最后n个字节。无论采用何种形式,如果{from}大于整个资源的总长度,这样的区间定义就被视为不合法。
如果请求的Range报头携带一个不合法的区间,服务端就会返回一个状态码为“416 Range Not Satisfiable”的响应,否则返回一个状态码为“206 Partial Content”的响应,响应的主体将只包含指定区间的内容。返回的内容在整个资源的位置通过响应报头Content-Range来表示,采用的格式为{from}-{to}/{length}。除此之外,还有一个与区间请求相关的响应报头Accept-Ranges,它表示服务端能够接受的区间类型。例如,前面针对条件请求的响应都具有一个Accept-Ranges: bytes报头,表示服务支持针对资源的区间划分。如果该报头的值被设置为none,则意味着服务端不支持区间请求。
区间请求在某些时候也会验证资源内容是否发生改变。在这种情况下,请求会利用一个名为If-Range的报头携带一个时间戳或者整个资源(不是当前请求的区间)的标签。服务端在接收到请求之后会根据这个报头判断请求的整个资源是否发生变化,如果判断已经发生变化,它会返回一个状态码为“200 OK”的响应,响应主体将包含整个资源的内容。只有在判断资源并未发生变化的前提下,服务端才会返回指定区间的内容。
针对静态文件的区间请求
下面从HTTP请求和响应报文的角度来探讨StaticFileMiddleware中间件针对区间请求的支持。我们依然沿用前面演示条件请求的实例,该实例中作为目标文件的foobar.txt包含26个字母和10个数字,加上UTF文本文件初始的3个字符(EF、BB、BF),所以总长度为39。我们发送如下两个请求分别获取前面26个字母(3-28)和后面10个数字(-10)。
GET http://localhost:50000/foobar.txt HTTP/1.1
Host: localhost:50000
Range: bytes=3-28
HTTP/1.1 206 Partial Content
Date: Wed, 18 Sep 2019 23:38:59 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 26
Content-Range: bytes 3-28/39
Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT
Accept-Ranges: bytes
ETag: "1d56e76ed13ed27"
abcdefghijklmnopqrstuvwxyz
GET http://localhost:50000/foobar.txt HTTP/1.1
Host: localhost:50000
Range: bytes=-10
HTTP/1.1 206 Partial Content
Date: Wed, 18 Sep 2019 23:39:51 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 10
Content-Range: bytes 29-38/39
Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT
Accept-Ranges: bytes
ETag: "1d56e76ed13ed27"
0123456789
由于请求中指定了正确的区间,所以我们会得到两个状态码为“206 Partial Content”的响应,响应的主体仅包含目标区间的内容。除此之外,响应报头Content-Range(“bytes 3-28/39”和“bytes 29-38/39”)指明了返回内容的区间范围和整个文件的总长度。目标文件最后修改的时间戳和标签同样会存在于响应报头Last-Modified与ETag之中。
接下来我们发送如下所示的一个区间请求,并刻意指定一个不合法的区间(50-)。正如HTTP规范所描述的那样,在这种情况下可以得到一个状态码为“416 Range Not Satisfiable”的响应。
GET http://localhost:5000/foobar.txt HTTP/1.1
Host: localhost:5000
Range: bytes=50-
HTTP/1.1 416 Range Not Satisfiable
Date: Wed, 18 Sep 2019 23:43:21 GMT
Server: Kestrel
Content-Length: 0
Content-Range: bytes */39
GET http://localhost:5000/foobar.txt HTTP/1.1
Range: bytes=-10
If-Range: Wed, 18 Sep 2019 01:01:01 GMT
Host: localhost:5000
HTTP/1.1 200 OK
Date: Wed, 18 Sep 2019 23:45:32 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 39
Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT
Accept-Ranges: bytes
ETag: "1d56e76ed13ed27"
abcdefghijklmnopqrstuvwxyz0123456789
GET http://localhost:50000/foobar.txt HTTP/1.1
User-Agent: Fiddler
Host: localhost:50000
Range: bytes=-10
If-Range: "123abc456"
HTTP/1.1 200 OK
Date: Wed, 18 Sep 2019 23:46:36 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 39
Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT
Accept-Ranges: bytes
ETag: "1d56e76ed13ed27"
abcdefghijklmnopqrstuvwxyz0123456789
静态文件中间件[1]: 搭建文件服务器
静态文件中间件[2]: 条件请求以提升性能
静态文件中间件[3]: 区间请求以提供部分内容
静态文件中间件[4]: StaticFileMiddleware
静态文件中间件[5]: DirectoryBrowserMiddleware & DefaultFilesMiddleware