最近进行一次下载请求,想使用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