node实现分片下载的示例代码


Posted in Javascript onOctober 17, 2018

本文基于http Range Requests协议,实现了分片下载的功能。

使用场景包括基于浏览器的流文件片段传输、基于客户端的分片下载等。

原理

http通过Range Requests相关的header,可以与服务器进行协商,实现分部分的请求。

这里就不细说具体协议内容了,具体可以参考这两篇文章,解释的非常详细:

  1. https://tools.ietf.org/html/rfc7233
  2. https://3water.com/article/68284.htm

下面贴一下实现过程。

服务端代码

服务端用node实现:

app.use(async ctx => {
 const file = path.join(__dirname, `${PATH}${ctx.path}`);
 // 1、404检查
 try {
  fs.accessSync(file);
 } catch (e) {
  return ctx.response.status = 404;
 }
 const method = ctx.request.method;
 const { size } = fs.statSync(file);
 // 2、响应head请求,返回文件大小
 if ('HEAD' == method) {
  return ctx.set('Content-Length', size);
 }
 const range = ctx.headers['range'];
 // 3、通知浏览器可以进行分部分请求
 if (!range) {
  return ctx.set('Accept-Ranges', 'bytes');
 }
 const { start, end } = getRange(range);
 // 4、检查请求范围
 if (start >= size || end >= size) {
  ctx.response.status = 416;
  return ctx.set('Content-Range', `bytes */${size}`);
 }
 // 5、206分部分响应
 ctx.response.status = 206;
 ctx.set('Accept-Ranges', 'bytes');
 ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
 ctx.body = fs.createReadStream(file, { start, end });
});

app.listen(3000, () => console.log('partial content server start'));

function getRange(range) {
 var match = /bytes=([0-9]*)-([0-9]*)/.exec(range);
 const requestRange = {};
 if (match) {
  if (match[1]) requestRange.start = Number(match[1]);
  if (match[2]) requestRange.end = Number(match[2]);
 }
 return requestRange;
}

代码实现的功能逻辑大致是:

  • 对请求的资源做检查,不存在则响应404
  • 对于HEAD请求,返回资源大小
  • 如果GET请求没有告知range,返回Content-Length,告知浏览器可以进行分片请求
  • 如果请求设置了range,则检查range是否合法,不合法返回合法的rangge
  • 一切正常,获取文件range范围部分,做流响应

代码很简单,把Range Requests协议对应实现一遍就ok了,当然这里没有完全实现协议的内容,但已经满足了这里演示的需求。

服务端代码ok了,用一个浏览器的demo来检验一下。

浏览器例子

现代浏览器基本都实现了Range Requests,这里用audio标签作为例子。

<html>
 <head>
  <title>分片流传输</title>
  <script type="text/javascript">
   function jump() {
    const player = document.getElementById('musicPlayer');
    // 从30s开始播放
    player.currentTime = 30;
   }
  </script>
 </head>
 <body>
  <audio id="musicPlayer" src="http:127.0.0.1:3000/source.mp3" controls></audio>
  <button onclick="jump()">切到30s</button>
 </body>
</html>

最终的效果是这样的:

node实现分片下载的示例代码

node实现分片下载的示例代码

对比两张图,当html加载完成,浏览器自动请求资源,此时header有Range: bytes=0-,表示从第0 byte开始加载资源;当点击跳到30s处播放时,此时header变成了Range: bytes=3145728-

同样用这个服务端代码,还可以实现一个客户端,模拟一下分包下载。

node分包下载

这个例子演示了,对一个资源,并发的实现分部分的下载,然后再合并成一个文件。

这里也是用node实现:

import request from 'request';
import path from 'path';
import fs from 'fs';

const SINGLE = 1024 * 1000;
const SOURCE = 'http://127.0.0.1:3000/source.mp3';

request({
 method: 'HEAD',
 uri: SOURCE,
}, (err, res) => {
 if (err) return console.error(err);
 const file = path.join(__dirname, './download/source.mp3');
 try {
  fs.closeSync(fs.openSync(file, 'w'));
 } catch (err) {
  return console.error(err);
 }
 const size = Number(res.headers['content-length']);
 const length = parseInt(size / SINGLE);
 for (let i=0; i<length; i++) {
  let start = i * SINGLE;
  let end = i == length ? (i + 1) * SINGLE - 1 : size - 1;
  request({
   method: 'GET',
   uri: SOURCE,
   headers: {
    'range': `bytes=${start}-${end}`
   },
  }).on('response', (resp) => {
   const range = resp.headers['content-range'];
   const match = /bytes ([0-9]*)-([0-9]*)/.exec(range);
   start = match[1];
   end = match[2];
  }).pipe(fs.createWriteStream(file, {start, end}));
 }
});

代码比较简单,就是开启多个http请求,并发的下载资源,然后根据响应的content-range,写到文件的对应位置。

参考文章:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
小型js框架veryide.librar源代码
Mar 05 Javascript
Javascript String对象扩展HTML编码和解码的方法
Jun 02 Javascript
js利用与或运算符优先级实现if else条件判断表达式
Apr 15 Javascript
js为数字添加逗号并格式化数字的代码
Aug 23 Javascript
javascript事件冒泡和事件捕获详解
May 26 Javascript
javaScript实现滚动新闻的方法
Jul 30 Javascript
基于JavaScript实现移除(删除)数组中指定元素
Jan 04 Javascript
vue mounted组件的使用
Jun 18 Javascript
Vue中对iframe实现keep alive无刷新的方法
Jul 23 Javascript
优雅的使用javascript递归画一棵结构树示例代码
Sep 22 Javascript
JavaScript组合模式---引入案例分析
May 23 Javascript
el-form 多层级表单的实现示例
Sep 10 Javascript
在小程序开发中使用npm的方法
Oct 17 #Javascript
浅谈HTTP 缓存的那些事儿
Oct 17 #Javascript
使用angular-cli webpack创建多个包的方法
Oct 16 #Javascript
element-ui的回调函数Events的用法详解
Oct 16 #Javascript
基于vue-upload-component封装一个图片上传组件的示例
Oct 16 #Javascript
Postman的下载及安装教程详解
Oct 16 #Javascript
Vue.js 时间转换代码及时间戳转时间字符串
Oct 16 #Javascript
You might like
PHP调用Webservice实例代码
2011/07/29 PHP
php实现评论回复删除功能
2017/05/23 PHP
php模式设计之观察者模式应用实例分析
2019/09/25 PHP
网页常用特效代码整理
2006/06/23 Javascript
用jQuery简化JavaScript开发分析
2009/02/19 Javascript
javascript中关于执行环境的杂谈
2011/08/14 Javascript
javascript 兼容所有浏览器的DOM扩展功能
2012/08/01 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
2014/07/10 Javascript
nodejs爬虫抓取数据之编码问题
2015/07/03 NodeJs
jquery实现平滑的二级下拉菜单效果
2015/08/26 Javascript
jQuery使用animate实现ul列表项相互飘动效果示例
2016/09/16 Javascript
在js里怎么实现Xcode里的callFuncN方法(详解)
2016/11/05 Javascript
jQuery设置和获取select、checkbox、radio的选中值方法
2017/01/01 Javascript
深入理解vue.js中的v-if和v-show
2017/06/22 Javascript
详解vue数据渲染出现闪烁问题
2017/06/29 Javascript
微信小程序实现弹出层效果
2020/05/26 Javascript
JS实现的自定义map方法示例
2019/05/17 Javascript
JS通过ajax + 多列布局 + 自动加载实现瀑布流效果
2019/05/30 Javascript
JS正则表达式常见函数与用法小结
2020/04/13 Javascript
[01:12]快闪回顾DOTA2亚洲邀请赛(DAC) 静候2018新征程开启
2018/03/11 DOTA
[58:37]Serenity vs Fnatic 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
Pyramid Mako模板引入helper对象的步骤方法
2013/11/27 Python
python抓取网页中的图片示例
2014/02/28 Python
numpy linalg模块的具体使用方法
2019/05/26 Python
Python Web框架之Django框架Model基础详解
2019/08/16 Python
Django stark组件使用及原理详解
2019/08/22 Python
利用 Python ElementTree 生成 xml的实例
2020/03/06 Python
Numpy中的数组搜索中np.where方法详细介绍
2021/01/08 Python
详解CSS 3 中的 calc() 方法
2018/01/12 HTML / CSS
深入理解HTML5定时器requestAnimationFrame的使用
2018/12/12 HTML / CSS
cosme官方海外旗舰店:日本最大化妆品和美容产品的综合口碑网站
2017/01/18 全球购物
实习生自我评价
2014/01/18 职场文书
爱国演讲稿400字
2014/05/07 职场文书
2014年项目经理工作总结
2014/11/24 职场文书
ORACLE数据库应用开发的三十个注意事项
2021/06/07 Oracle
笔记本自带的win11如何跳过联网激活?
2022/04/20 数码科技