多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
- server - 思路 - 示例 - client - 测试 [TOC] ## 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.toString(); if(chunk.includes('p')){ pause = true }else{ pause = false; download(); } }); //--- --- --- 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 = []; 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+10; end = nextEnd+1<total?nextEnd:total; download(); } },1000) }) }) } ``` 以上实现了一个支持暂停下载的断点续传demo, 唯一要稍微注意一点的是,我们是通过`Range:Bytes=x-xx`这头来控制下载的。 ## 测试 ``` curl -v -H 'Range:bytes=0-9' http://localhost:8080 //本文中的栗子请求的数据索引是包前又包后的 ``` --- End