斷點續傳的服務器主要是要注意一個請求頭——Range
,嗯,在node裏接收是這樣的node
let range = req.headers['range'];
<<<
bytes = 0-999
複製代碼
其中bytes = 0-999
便是Range
的值,因而乎咱們就能利用整個值來控制讀取的start
和end
。bash
[info]值得注意的是,客戶端請求的這個range僅僅只是一個口頭要求,並不具有任何實質性的影響力,要怎麼返回數據給客戶端仍是服務端說了算,so究竟從什麼數字編碼開始纔是一個資源文件開始的索引位置?嗯,只要服務器願意,能夠是0也能夠是1甚至能夠是10086。服務器
拿到range後,咱們就能夠將range中的x-x
和咱們createStream
的start/end
造成映射,從而達到控制輸出的目的。curl
fs.createReadStream(p, { start, end }).pipe(res);
複製代碼
[danger] 注意: createStream API 的索引位置是包前又包後的。async
另外還有一點須要注意的是咱們要返回給客戶端兩個頭測試
Accept-Ranges:bytes
ui
代表服務器是否支持指定範圍請求及哪一種類型的分段請求編碼
Content-Range:bytes start-end/total
:url
告訴他此次咱們返回的數據是哪裏到哪裏的,數據總共有多少字節,這樣客戶端才能知道下次該從哪裏請求數據,是否已經拿完數據該結束請求了。spa
介於建立可寫可讀流指定索引時是包前又包後的,
我的推設置 薦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);
複製代碼
知道了服務端是怎麼控制輸出的,客戶端就簡直了~
要不,咱們就直接上代碼?
...
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<total){
start = end+1;
nextEnd = end+speed;
end = nextEnd+1<total?nextEnd:total;
download();
}
},1000)
})
})
}
複製代碼
以上實現了一個支持暫停下載的斷點續傳demo,
惟一要稍微注意一點的是,咱們是經過Range:Bytes=x-xx
這頭來控制下載的。
curl -v -H 'Range:bytes=0-9' http://localhost:8080 //本文中的栗子請求的數據索引是包前又包後的
複製代碼
End