圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

承香墨影發表於2018-07-23

圖解: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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

例如這裡給了一個 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 範圍請求,助力斷點續傳、多執行緒下載的核心原理

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

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

2.3 資源變化

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

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

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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

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

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

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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

此時,如果兩次操作的都是同一個資原始檔,就會繼續返回 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 的事務報文。

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

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

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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

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

四、範圍請求小結

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

再重新整理一下關鍵點:

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

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

3. 通過在請求頭中新增 Range 這個請求頭,來指定請求的內容實體的位元組範圍。

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

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

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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

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

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


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

推薦閱讀:

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

圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理

相關文章