圖解:HTTP 范圍請求,助力斷點續傳、多線程下載的核心原理


題圖:by Charles Loyer

一、序

Hi,大家好,我是承香墨影!

HTTP 協議在網絡知識中占據了重要的地位,HTTP 協議最基礎的就是請求和響應的報文,而報文又是由報文頭(Header)和實體組成。大多數 HTTP 協議的使用方式,都是依賴設置不同的 HTTP 請求/響應 的 Header 來實現的。

本系列《實用 HTTP》就拋開常規的 Header 講解式的表述方式,從實際問題出發,來分析這些 HTTP 協議的使用方式,到底是為了解決什么問題?同時講解它是如何設計的和它實現原理。

HTTP 協議是一種無狀態的“松散協議”,它不會記錄不同請求的狀態,並且因為它本身包含了兩端(客戶端和服務端),根據請求和響應來區分,它大部分的內容都只是一個建議,其實雙邊是可以不遵守此建議的。

“這里寫了建議零售價 2 元...”

“哦,不接受建議!”

文本是本系列的第五篇,前四篇傳送門:

今天再來介紹一下 HTTP 的范圍請求。范圍請求主要是針對較大的文件的請求或者上傳,可以僅操作它的某一段。

一個比較常見的場景,就是斷點續傳/下載,在網絡情況不好的時候,可以在斷開連接以后,僅繼續獲取部分內容。例如在網上下載軟件,已經下載了 95% 了,此時網絡斷了,如果不支持范圍請求,那就只有被迫重頭開始下載。但是如果有范圍請求的加持,就只需要下載最后 5% 的資源,避免重新下載。

另一個場景就是多線程下載,對大型文件,開啟多個線程,每個線程下載其中的某一段,最后下載完成之后,在本地拼接成一個完整的文件,可以更有效的利用資源。

這算是兩個比較常見的場景,接下來我們來看看范圍請求的 HTTP 協議支持的技術細節。

二、HTTP 的范圍請求

2.1 是否支持范圍請求

HTTP 本身是一種無狀態的“松散”協議,而在經歷了很多版本的迭代之后,只在 HTTP/1.1(RFC2616) 之上,才支持范圍請求。所以如果客戶端或者服務端兩端的某一端低於 HTTP/1.1,我們就不應該使用范圍請求的功能。

而在 HTTP/1.1 中,很明確的聲明了一個響應頭部 Access-Ranges 來標記是否支持范圍請求,它只有一個可選參數 bytes

例如這里給了一個 MP4 的響應頭,可以看到它是有 Accept-Ranges:bytes 來標記的,有此標記標識當前資源支持范圍請求。

2.2 使用范圍請求

如果已經確定雙端都支持范圍請求,我們就可以在請求資源的時候使用它。

所有的文件最終都是存儲在磁盤或者內存中的字節,對於待操作的文件可以將其以字節為單位分割。這樣只需要 HTTP 支持請求該文件從 n 到 n+x 這個范圍內的資源,就可以實現范圍請求了。

HTTP/1.1 中定義了一個 Ranges 的請求頭,來指定請求實體的范圍。它的范圍取值是在 0 - Content-Length 之間,使用 - 分割。。

例如已經下載了 1000 bytes 的資源內容,想接着繼續下載之后的資源內容,只要在 HTTP 請求頭部,增加 Ranges:bytes=1000- 就可以了。

Range 還有幾種不同的方式來限定范圍,可以根據需要靈活定制:

1. 500-1000:指定開始和結束的范圍,一般用於多線程下載。

2. 500- :指定開始區間,一直傳遞到結束。這個就比較適用於斷點續傳、或者在線播放等等。

3. -500:無開始區間,只意思是需要最后 500 bytes 的內容實體。

4. 100-300,1000-3000:指定多個范圍,這種方式使用的場景很少,了解一下就好了。

HTTP 協議是一種雙邊協商的協議,既然請求頭部已經確定是使用 Ranges 了,還有響應頭部中,也需要使用 Content-Ragne 這個響應頭來標記響應的實體內容范圍。

Content-Range 的格式也很清晰,首先標記它的單位是 bytes 然后標記當前傳遞的內容實體范圍和總長度。

Content-Range: bytes 100-999/1000

在這個例子中,會傳遞 100 ~ 999 范圍的內容實體,而該資源文件的總大小是 1000 bytes。並且此時的 HTTP 響應狀態碼為 206 Partial Content

HTTP 206 Partial Content 成功狀態響應代碼表示請求已成功,並且主體包含所請求的數據區間,該數據區間是在請求的 Range 首部指定的。

有關 206 狀態碼的解釋可以參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/206

所以一個正常的流程應該如下圖所示:

注意這里的每個 HTTP 事務中的響應頭里,都是會包含 Content-Length 的,只是它包含的是當前范圍請求響應的內容實體長度,而非此資源完整的長度。

到這里基本上算是講清楚 HTTP 范圍請求的正確流程了,接下來看看一些特殊的情況。

2.3 資源變化

當我們在一些下載工具中,下載大尺寸資源的時候,偶爾中間暫停過再重新下載,可能會遇見它又重頭開始下載的情況。

這看似是 HTTP 的范圍請求失效了,但是實際上並不一定如此,很可能是因為請求的資源,在請求的這個過程中,發生了改變。

假如你下載的過程中,下載的源資源文件發生了變化,但是 URL 沒有改變,此時文件長度可能已經變化了(這是非常容易發現的),極端情況下就算沒有長度沒有變化,你再繼續下載,很可能最終下載完成之后,無法將下載的內容拼接成我們需要的文件。

如果我們需要從服務器上下載某個資源,一定要預防此資源可能發生的變動。在之前講 HTTP 緩存的時候講到,在 HTTP 協議中,可以通過 ETag 或者 Last-Modified 來標識當前資源是否變化。

  • ETag:當前文件的一個驗證令牌指紋,用於標識文件的唯一性。
  • Last-Modified:標記當前文件最后被修改的時間。

在 HTTP 的范圍請求中,也可以使用這兩個字段來區分分段請求的資源,是否有修改過,只需要在請求頭中,將它放在 If-Range 這個請求報文頭中即可。If-Range 使用 ETag 或者 Last-Modified 兩個參數任意一個,原樣填入即可。

此時,如果兩次操作的都是同一個資源文件,就會繼續返回 206 狀態碼,開始后續的操作,反之則會返回 200 狀態碼,表示文件發生改變,要從頭下載。

需要注意的是 If-Range 需要和 Range 配合起來使用,否則會被服務端忽略。

再額外提一點,如果客戶端請求報文頭中,對 Range 填入的范圍錯誤,會返回 416 狀態碼。

HTTP 416 Range Not Satisfiable 錯誤狀態碼意味着服務器無法處理所請求的數據區間。最常見的情況是所請求的數據區間不在文件范圍之內,也就是說,Range 首部的值,雖然從語法上來說是沒問題的,但是從語義上來說卻沒有意義。

有關 416 狀態碼,可以參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/416

三、范圍請求的例子

3.1 用 Chrome 播放一個適配

前面介紹的概念,很多技術點其實描述的都是某一個請求片段,接下來我們以一個實際的例子來說明范圍請求的具體細節。

在這個例子中,我找了一個視頻的播放地址,直接在 Chrome 中進行播放。正常播放之后,再隨手拖動視頻進度,之后無操作讓其自動播放一段時間,來看看 HTTP 的事務報文。

簡單描述一下情況,自然播放的時候,會首先想資源的 URL 發送請求,返回 200 的響應碼,可以判斷出當前資源支持 Accept-Ranges,接下來會去使用 Range 發送范圍請求,得到的響應碼就是 206,並返回對應范圍的實體內容。而在每次拖動進度的時候,都會去重新發送一個范圍請求,依照拖動的進度來計算請求范圍。此處不存在資源被修改的情況,所以不會出現重新請求下載的情況。

就不一個一個對 HTTP 事務截圖了,大概抽象了一下流程,如下圖所示:

可以看到,一次資源下載其實包含了很多次的請求過程,我們需要站在全局的角度來看到它。

四、范圍請求小結

到這里我們就已經把 HTTP 范圍請求的整個流程都說明清楚了。

再重新整理一下關鍵點:

1. HTTP 范圍請求,需要 HTTP/1.1 及之上支持,如果雙端某一段低於此版本,則認為不支持。

2. 通過響應頭中的 Accept-Ranges 來確定是否支持范圍請求。

3. 通過在請求頭中添加 Range 這個請求頭,來指定請求的內容實體的字節范圍。

4. 在響應頭中,通過 Content-Range 來標識當前返回的內容實體范圍,並使用 Content-Length 來標識當前返回的內容實體范圍長度。

5. 在請求過程中,可以通過 If-Range 來區分資源文件是否變動,它的值來自 ETag 或者 Last-Modifled。如果資源文件有改動,會重新走下載流程。

再配一張流程圖,就更清晰了。

到此 HTTP 范圍請求的所有關鍵技術點,就已經講解清楚。范圍請求被用在諸如:斷點續傳、多線程下載等場景下,大部分 CDN 上的資源都是支持范圍請求的,具體你能在什么場景下應用,就看你的想象力了。

還有什么更多的想法,歡迎留言討論。


公眾號后台回復成長『成長』,將會得到我准備的學習資料,也能回復『加群』,一起學習進步;你還能回復『提問』,向我發起提問。

推薦閱讀:

Android P 適配經驗 | 技術創業選擇清單 | HTTP傳輸編碼 | 什么正在消耗你? | HTTP 內容編碼 | 圖解 HTTP 緩存 | 聊聊 HTTP 的 Cookie | 輔助模式實戰 | Accessibility 輔助模式 | 小程序 Flex 布局 | 好的 PR 讓你更靠譜 | 密碼管理之道


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM