- server
- 思路
- 关于索引位置协商
- 示例
- 思路
- client
- 测试
server
思路
断点续传的服务器主要是要注意一个请求头——Range
,嗯,在node里接收是这样的
let range = req.headers['range'];
<<<
bytes = 0-999
复制代码
其中bytes = 0-999
即是Range
的值,于是乎我们就能利用整个值来控制读取的start
和end
。
[info]值得注意的是,客户端请求的这个range仅仅只是一个口头要求,并不具备任何实质性的影响力,要怎么返回数据给客户端还是服务端说了算,so究竟从什么数字编码开始才是一个资源文件开始的索引位置?嗯,只要服务器愿意,可以是0也可以是1甚至可以是10086。
拿到range后,我们就可以将range中的x-x
和我们createStream
的start/end
形成映射,从而达到控制输出的目的。
fs.createReadStream(p, { start, end }).pipe(res);
复制代码
[danger] 注意: createStream API 的索引位置是包前又包后的。
另外还有一点需要注意的是我们要返回给客户端两个头
-
Accept-Ranges:bytes
表明服务器是否支持指定范围请求及哪种类型的分段请求
-
Content-Range:bytes start-end/total
:告诉他这次我们返回的数据是哪里到哪里的,数据总共有多少字节,这样客户端才能知道下次该从哪里请求数据,是否已经拿完数据该结束请求了。
关于索引位置协商
介于创建可写可读流指定索引时是包前又包后的,
个人推设置 荐Range:bytes=start-end
时候,第一个字节因以1开始,并且请求的数据要包括end(即要包后),然后我们在服务器端用fs.createReadStream()
设置rs的start和end时候统一将从请求头获取的Range中的start和end减一。
>>> Range:bytes=1-9
...
let range = req.headers['range'];
let result = range.match(/bytes=(\d*)-(\d*)/);
let start = result[0];
let end = result[1];
...
res.setHeader('Accept-Range','bytes');
res.setHeader('Content-Range',`bytes ${start}-${end}/${statObj.size}`) //1-9/total
res.statusCode = 206;
...
fs.createReadStream(filepath,{
start:start-1,end:end-1 //0-8
});
...
>>> Range:bytes=10-18
>>> ...
复制代码
示例
let http = require('http');
let fs = require('fs');
let path = require('path');
let { promisify } = require('util');
let stat = promisify(fs.stat);
let server = http.createServer(async function (req, res) {
let p = path.join(__dirname, 'content.txt');
let statObj = await stat(p);
let total = statObj.size;
let start = 0;
let end = total;
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]):end;
res.setHeader('Content-Range',`bytes ${start}-${end}/${total}`)
}
res.setHeader('Content-Type', 'text/plain;charset=utf8');
// res.write('输出开始');
fs.createReadStream(p, { start, end }).pipe(res);
});
server.listen(8080);
复制代码
client
知道了服务端是怎么控制输出的,客户端就简直了~
要不,我们就直接上代码?
...
let options = {
hostname:'localhost',
port:8080,
path:'/',
method:'GET'
}
let ws = fs.createWriteStream('./download.txt');
let pause = false;
let start = 0;
let speed = 10;
let end = start+speed;
download();
process.stdin.on('data',function(chunk){
chunk = chunk.slice(0,chunk.length-2);
option = chunk.toString();
switch(option){
case 'p':
pause = true;
break;
case 'c':
pause = false;
download();
default:
if(/^s\s-[0-9]+$/.test(option)){
option = option.slice(3);
speed = parseInt(option);
}
}
});
//--- --- ---
function download(){
options.headers = {
Range:`bytes=${start}-${end}` //请求头看这里
}
http.get(options,function(res){
let range = res.headers['content-range'];
let total = range.split('/')[1];
let buffers = [];
let nextEnd;
res.on('data',function(chunk){
buffers.push(chunk);
});
res.on('end',function(){
ws.write(Buffer.concat(buffers));
setTimeout(function(){
if(pause === false&&start复制代码
以上实现了一个支持暂停下载的断点续传demo,
唯一要稍微注意一点的是,我们是通过Range:Bytes=x-xx
这头来控制下载的。
测试
curl -v -H 'Range:bytes=0-9' http://localhost:8080 //本文中的栗子请求的数据索引是包前又包后的
复制代码
- p:暂停下载
- c:继续下载
- s -xx:修改下载速度
End