断点下载方案设计

大文件如果中间出现问题,还需要重新下载。断点下载中间如果出现问题,可以继续从中断处下载。

206 HTTP 状态码

范围请求成功,并且主体包含请求的数据区间

请求头

  • Range 请求头:指定请求实体范围,取值范围 0-Content-Length 之间

响应头

  • Accept-Ranges:bytes 表示当前资源支持范围请求

客户端实现

首先发一个请求,判断该资源是否支持范围请求。可以发一个 Range:bytes=0-1 的请求,判断回调中的响应头。(也可以用 HEAD 请求,只返回响应头判断)

然后单独保存一个文件,用于记录当前文件的下载情况(下载到哪一段了)

1
{"url":"https://dldir1.qq.com/qqfile/qq/QQ9.0.8/24209/QQ9.0.8.24209.exe","etag":null,"fileName":"QQ9.0.8.24209.exe","contentLength":75555704,"contentType":"application/octet-stream","blocks":[{"strat":0,"end":4194303},{"strat":4194304,"end":8388607},{"strat":8388608,"end":12582911},{"strat":12582912,"end":16777215},{"strat":16777216,"end":20971519},{"strat":20971520,"end":25165823},{"strat":25165824,"end":29360127},{"strat":29360128,"end":33554431},{"strat":33554432,"end":37748735},{"strat":37748736,"end":41943039},{"strat":41943040,"end":46137343},{"strat":46137344,"end":50331647},{"strat":50331648,"end":54525951},{"strat":54525952,"end":58720255},{"strat":58720256,"end":62914559},{"strat":62914560,"end":67108863},{"strat":67108864,"end":71303167},{"strat":71303168,"end":75497471},{"strat":75497472,"end":75555704}],"pointer":4}

pointer 就是用来保存当前下载的段数的(可按照一定比例拆分整个文件)。分段发送请求之前,先要判断一下目标的 Etag 和当前保存的信息的 Etag 是否相同,如果不相同,说明资源更新了,需要重新下载。

如何追加数据?

Node 中提供了 appendFileSync,用于向目标追加数据。

服务端实现

需要支持范围请求,服务端需要通过流式下载,可以直接读取中间某一段数据返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getStream(req, res, filepath, statObj) {
let start = 0;
let end = statObj.size - 1;
let range = req.headers['range'];
if (range) {
res.setHeader('Accept-Range', 'bytes');
res.statusCode = 206;
let result = range.match(/bytes=(\d*)-(\d*)/);
if (result) {
start = isNaN(result[1]) ? start : parseInt(result[1]);
end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
}
}
return fs.createReadStream(filepath, {
start, end
});
}