我们写代码的有时候工作中会做一些上传/下载图片或者文件的操作。但是我们也知道一次性下完是不可取的,对性能不是很友好,那这时候我们就想了个方法---断点续传。
你看,平时听一首歌的时候,如果听到一半(网络下载了一半),网络断掉了,用户需要继续听的时候,文件服务器不支持断点的话,则用户需要重新下载这个文件,多费流量啊。那如果我们一片一片的传,记录一个点,每次从记录的点往后传就十分亲民了。
获取部分内容的范围请求
其实啊,为了实现中断恢复下载的需求,需要能下载指定下载的实体范围:
- 请求头中的Range来指定 资源的byte范围
- 响应会返回状态码206响应报文
- 对于多重范围的范围请求,响应会在首部字段Content-Type中标明multipart/byteranges
我们来模拟一下:
先打开我们的命令行工具,写上如下代码
(后面是我在百度上找的一个图片)
curl -v --header "Range:bytes=0-10" http://pic26.nipic.com/20130117/10558908_124756651000_2.jpg
运行一下得到的这个结果:
从http那段服务器返回给我们的信息可以看出如下信息(206代表服务器答应我们的range请求方式)
- Content-Type: image/jpeg 类型是图片
- Accept-Ranges: bytes 单位是bytes
- Content-Range: bytes 0-10/292871 请求段为0-10,总共有292871bytes
- Content-Length: 11 请求长度为11
知道这些之后我们可以简单模拟一下客户端和服务器端怎么做的:
服务器端代码
首先客户端要发一个头Range:bytes=0-10,然后服务端返回一个头
// Accept-Ranges:bytes
// Content-Range:0-10/总大小
// 获取范围请求
let http = require('http');
let fs = require('fs');
let path = require('path');
let { promisify } = require('util');//这个模块是包装某个东西成为promise的。
let stat = promisify(fs.stat);
let server = http.createServer(async function (req, res) {
let p = path.join(__dirname, 'con.txt');
// 判断当前文件的大小
let statObj = await stat(p);
let start = 0;
let end = statObj.size - 1; // 读流是包前又包后的:statObj.size 是文件的大小。
let total = end
let range = req.headers['range'];
if (range) {//判断客户端是否有带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;
// 获取成功并且文件总大小是多少
// Content-Range:0-10/总大小
res.setHeader('Content-Range',`${start}-${end}/${total}`)
}
res.setHeader('Content-Type', 'text/plain;charset=utf8');
fs.createReadStream(p, { start, end }).pipe(res);
});
server.listen(3000);
这样一个简单的服务器端就写好了。
客户端代码
//首先要写一个options
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')){ //输入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 = [];//创建一个缓存区,把读到的数据都放在里面,等到end的时候就整个取出来。
res.on('data',function(chunk){
buffers.push(chunk);
});
res.on('end',function(){
//将获取的数据写入到文件中
ws.write(Buffer.concat(buffers));
setTimeout(function(){
if(pause === false&&start<total){
download();
}
},1000)
})
})
}
download();
代码非常浅显易懂。
最后我们在同级目录下创建一个con.txt文件。用node执行一下客户端文件,就会实现功能啦。
是不是跃跃欲试??
如果对你有帮助请点个赞噻!