最近進行一次下載請求,想使用onprogress顯示進度時發現,onprogress中顯示的total總爲0。html
爲何呢?nginx
不知道你們有沒有遇到過有時候用下載軟件下載文件的時候,有些下載能夠顯示總大小,有些不可顯示,看着就好像出了bug同樣。其實這緣由和onprogress中顯示的total總爲0的狀況差很少。
ajax
想要弄清楚緣由這時候要先了解下載文件的原理,而一般的文件下載是可斷點續傳,能夠從這方面入手。瀏覽器
爲了讓文件下載能夠暫停而後從新從暫停下載部分開始從新下載,這時候就要去了解HTTP中的content-length
、Accept-Ranges
、Content-Range
還有Range
。服務器
content-length
:用於響應頭,表示響應內容的字節大小app
Accept-Ranges
:用於響應頭,告知客戶端能夠進行範圍請求,後面的值表示返回的內容單位,一般是bytes,如:Accept-Ranges:bytes框架
Content-Range
: 用於響應頭,用於描述響應請求內容的範圍和總體長度,好比Content-Range: bytes 201-220/326
表示服務器端返回請求資源中的201到220bytes範圍的內容,請求資源總大小爲326字節,若是總大小未知就會顯示Content-Range: bytes 201-220/\*
koa
Range
:用於請求頭,做用是告知服務器端返回哪一部分的內容,好比Range:bytes=500-1000
表示告知服務器我要拿這個文件中500至1000字節的內容。async
利用HTTP
中的Range
、Content-Range
就能夠實現斷點下載。ui
咱們能夠用ajax
來模擬一下斷點下載,代碼以下,其中請求的是nginx
服務器的一個index.html
文件。
let entryContentLength = 0, entryContent = ""; getContentLength("http://localhost:8083/test/index.html").then(res => { if (res) { entryContentLength = res; } else { entryContentLength = "沒法獲知長度"; } sectionDownload(0, 20, "http://localhost:8083/test/index.html"); }); function sectionDownload(start, end, url) { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.setRequestHeader("Range", `bytes=${start}-${end}`); xhr.onload = function() { if (xhr.status == 206) { entryContent += xhr.response; //請求其中的某個部分 sectionDownload(end + 1, end + 20, url); } else if (xhr.status == 416) { //徹底下載後一系列操做 console.log( "獲取的內容爲:\n" + entryContent, "\n內容長度:\n" + entryContentLength ); } else if (xhr.status == 200) { console.log( "獲取的內容爲:\n" + entryContent, "\n內容長度:\n" + entryContentLength ); } else { console.log(xhr); } }; xhr.send(); } function getContentLength(url) { return new Promise(resolve => { const xhr = new XMLHttpRequest(); xhr.open("HEAD", url); xhr.onload = function() { resolve(xhr.getResponseHeader("content-length")); }; xhr.send(); }); }
說一下這段代碼的邏輯,這段代碼先向服務器發送一個HEAD
請求獲取響應頭content-length的大小,,也就是請求index.html
的大小,以後開始獲取index.html
內容,每次只獲取20字節,並拼湊到entryContent
變量中。最終沒有字節返回時,那麼entryContent
就是整個index.html的內容了。
有內容返回時HTTP響應頭:
請求範圍沒法知足時的HTTP響應頭:
你們能夠看到,當響應中有部分字節返回時,返回的狀態是206
,當客戶端請求的字節範圍超過了請求資源的大小時,狀態碼返回的是416
,206
狀態碼錶示抓取到了資源的部分數據,416
表示Range
請求的資源範圍沒法知足。咱們能夠根據這個返回狀態判斷是否繼續請求,從而判斷文件下載是否完成。
那麼咱們回到一開頭的問題,這時候你就發現文件總大小是從一開始就獲取到了,爲啥有的下載顯示下載的文件大小,有些不顯示了呢。
這時候若是你服務器開啓gzip
壓縮,你用HEAD
請求發現後,你會發現HTTP
響應頭沒有content-length
返回,下面的這張圖是我Nginx
開啓了gzip
壓縮後進行HEAD
請求時服務器返回的響應,能夠發現響應頭是沒有content-length
。這時候你是否是知道爲何有時下載文件的時候是不顯示總大小。
有時候服務器若是開啓壓縮或者爲了減小cpu壓力等等,是不會去計算文件的總大小的,這時候從響應頭中就沒法獲取資源的總大小
。
然而若是你使用的是Node
服務器,不使用任何插件
,你發現就算你請求帶上了Range:bytes=xx-xx
等請求頭,文件內容仍是完整獲取,這時你就會發現,分段請求下載這種能力是依靠服務器才能實現
,Nginx、Apache等服務器都有他們本身的實現方法,那麼Node
服務器如何實現呢?
下面的代碼基於koa
框架的實現具備分段下載文件的功能。
const fs = require("fs"); const path = require("path"); const Koa = require("koa"); const app = new Koa(); const PATH = "./public"; app.use(async ctx => { const file = path.join(__dirname, `${PATH}${ctx.path}`); // 一、404檢查 try { fs.accessSync(file); } catch (e) { return (ctx.response.status = 404); } //ctx.set('content-encoding', 'gzip'); const method = ctx.request.method; const { size } = fs.statSync(file); // 二、響應head請求,返回文件大小 if ("HEAD" == method) { return ctx.set("Content-Length", size); } const range = ctx.headers["range"]; // 三、通知瀏覽器能夠進行分部分請求 if (!range) { //這裏若是客戶端不是分段請求就返回整個文件 ctx.body = fs.createReadStream(file); return ctx.set("Accept-Ranges", "bytes"); } else { const { start, end } = getRange(range); // 四、檢查請求範圍 if (start >= size) { ctx.response.status = 416; return ctx.set("Content-Range", `bytes */${size}`); } // 五、206分部分響應 ctx.response.status = 206; ctx.set("Accept-Ranges", "bytes"); ctx.set("Content-Range", `bytes ${start}-${end ? end : size - 1}/${size}`); ctx.body = fs.createReadStream(file, { start, end }); } }); app.listen(3000, () => console.log("partial content server start")); function getRange(range) { const match = /bytes=([0-9]*)-([0-9]*)/.exec(range); const requestRange = {}; if (match) { if (match[1]) requestRange.start = Number(match[1]); if (match[2]) requestRange.end = Number(match[2]); } return requestRange; }
你們能夠看到其實分段下載很簡單,就是Node
根據請求頭的Range
進行分段讀取文件二進制流。
參考:
https://blog.csdn.net/weixin_33836874/article/details/88720882