ASP.NET Core靜態文件中間件[3]: 區間請求以提供部分內容
- 2020 年 12 月 17 日
- 筆記
- .NET Core, [01] 技術剖析, [02] 編程技巧, Asp.Net Core, Static File
大部分針對物理文件的請求都希望獲取整個文件的內容,區間請求則與之相反,它希望獲取某個文件部分區間的內容。區間請求可以通過多次請求來獲取某個較大文件的全部內容,並實現斷點續傳。如果同一個文件同時存放到多台伺服器,就可以利用區間請求同時下載不同部分的內容。與條件請求一樣,區間請求也作為標準定義在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 //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 //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 //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
為了驗證區間請求針對文件更新狀態的檢驗,我們使用了請求報頭If-Range。在如下所示的這兩個請求中,我們分別將一個基準時間戳和文件標籤作為這個報頭的值,顯然服務端針對這兩個報頭的值都將做出「文件已經更新」的判斷。根據HTTP規範的約定,這種請求會返回一個狀態碼為「200 OK」的響應,響應的主體將包含整個文件的內容。如下所示的響應報文就證實了這一點。
GET //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 //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