Range 断点续传
有时候下载文件下载到一半的时候网络断开了, 需要继续下载的时候, 如果不支持断点, 用户需要重新下载, 如果支持 range, 客户端会记录已经下载的内容,当网络恢复时, 则向服务端发送剩下 range 的读取请求, 服务端接收到请求后,会根据 range 找到剩下的内容 发送给 客户端。
- 客户端也可以通过
curl -v --header 'Range: bytes=xx-xx' http://localhost:3000
来指定资源的 byte 范围, 但是具体怎么返回还是服务端说的算, 从什么位置开始返回 也是服务端说的算 可以是 0 也可以是 100 - server 通过请求头获取
Range: bytes=xx-xx
来判断是否支持 range , 如果这个值存在 且 有效,通过
res.setHeader('Accept-Ranges','bytes');res.setHeader('Content-Range','${start}-${end}/${total}')
告知客户端支持 range , 每次发送内容的 bytes 范围, 并将对应的内容发送给 客户端 ,响应的状态码变成 206,表示 Partial Content,并设置 Content-Range。如果无效,则返回 416 状态码,表明 Request Range Not - 客户端 通过
response.headers['content-range']
可以获取 range 范围 及 总数, 以便客户端知道什么时候该结束请求
**客户端代码**
let options = {
hostname: 'localhost',
port: 3000,
path: '/',
method: 'GET'
};
let fs = require('fs');
let path = require('path');
let http = require('http');
let ws = fs.createWriteStream('./download.txt');
let pause = false; // 是否暂停
let start = 0; // 开始位置
// 下载 = 每次获取 10 个下载
process.stdin.on('data', function (chunk) {
chunk = chunk.toString();
if (chunk.includes('p')) {
// 断开下载
pause = true;
} else {
// 继续下载
pause = false;
download();
}
});
function download () {
options.headers = {
Range: `bytes=${start}-${start + 10}`
}
start += 10;
// 发送请求
http.get(options, function (res) {
let range = res.headers['content-range'];
let total = range.split('/')[1];
let buffers = [];
res.on('data', function (chunk) {
buffers.push(chunk);
});
res.on('end', function () {
ws.write(Buffer.concat(buffers)); // 将 buffers 转为字符串写入到文件中
setTimeout(function () {
if (pause === false && start < total) {
download();
}
}, 1000)
})
})
}
download();
**服务端**
let http = require('http');
let fs = require('fs');
let path = require('path');
let { promisify } = require('util');
let stat = promisify(fs.stat);
/**
* 客户端会发送一个头 Range: bytes=0-10
* 服务端返回一个头
* Accept-Ranges: bytes
* Content-Range: 0-10/总大小
*/
let serevr = http.createServer(async function (req, res) {
let p = path.join(__dirname, './content.txt');
let statObj = await stat(p);
let start = 0;
let end = statObj.size - 1; // 读流是包前又包后的
let total = end;
let range = req.headers['range'];
if (range) {
res.setHeader('Accept-Ranges', 'bytes');
let result = range.match(/bytes=(\d*)-(\d*)/);
start = result[1] ? parseInt(result[1]) : start;
end = result[2] ? parseInt(result[2]) - 1 : end; // 因为流的 end 是 包前又包后的 此次这个地方需要减去 1
// 告知客户端获取成功
res.setHeader('Content-Range', `${start}-${end}/${total}`);
}
res.setHeader('Content-Type', 'text/plain;charset=utf8');
fs.createReadStream(p, { start, end }).pipe(res);
});
serevr.listen(3000);