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利用Array.splice实现Array的insert/remove
Jan 13 Javascript
Extjs入门之动态加载树代码
Apr 09 Javascript
关于jQuery的inArray 方法介绍
Oct 08 Javascript
jQuery使用技巧简单汇总
Apr 18 Javascript
js/html光标定位的实现代码
Sep 23 Javascript
JavaScript实现选择框按比例拖拉缩放的方法
Aug 04 Javascript
微信小程序实战之顶部导航栏(选项卡)(1)
Jun 19 Javascript
如何理解Vue的.sync修饰符的使用
Aug 17 Javascript
JavaScript使用FileReader实现图片上传预览效果
Mar 27 Javascript
详解Vue中组件的缓存
Apr 20 Javascript
jquery图片预览插件实现方法详解
Jul 18 jQuery
Angular处理未可知异常错误的方法详解
Jan 17 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
ThinkPHP使用smarty模板引擎的方法
2014/07/01 PHP
非常实用的PHP常用函数汇总
2014/12/17 PHP
php字符串过滤与替换小结
2015/01/26 PHP
简单的无缝滚动程序-仅几行代码
2007/05/08 Javascript
CCPry JS类库 代码
2009/10/30 Javascript
JavaScript让IE浏览器event对象符合W3C DOM标准
2009/11/24 Javascript
Web 前端设计模式--Dom重构 提高显示性能
2010/10/22 Javascript
Ajax局部更新导致JS事件重复触发问题的解决方法
2014/10/14 Javascript
原生javascript实现DIV拖拽并计算重复面积
2015/01/02 Javascript
JavaScript检测实例属性, 原型属性
2015/02/04 Javascript
jquery实现上传文件大小类型的验证例子(推荐)
2016/06/25 Javascript
jquery轮播的实现方式 附完整实例
2016/07/28 Javascript
AngularJS 模块详解及简单实例
2016/07/28 Javascript
JS+CSS3模拟溢出滚动效果
2016/08/12 Javascript
JavaScript实现左右下拉框动态增删示例
2017/03/09 Javascript
jQuery列表检索功能实现代码
2017/07/17 jQuery
微信小程序实现长按删除图片的示例
2018/05/18 Javascript
Angular7创建项目、组件、服务以及服务的使用
2019/02/19 Javascript
微信小程序利用云函数获取手机号码
2019/12/17 Javascript
[46:21]Liquid vs LGD 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
Python ORM框架SQLAlchemy学习笔记之映射类使用实例和Session会话介绍
2014/06/10 Python
Python实现搜索算法的实例代码
2020/01/02 Python
python新式类和经典类的区别实例分析
2020/03/23 Python
Python Selenium XPath根据文本内容查找元素的方法
2020/12/07 Python
一款纯css3制作的2015年元旦雪人动画特效教程
2014/12/29 HTML / CSS
CSS3哪些新特性值得称赞
2016/03/02 HTML / CSS
浅谈pc和移动端的响应式的使用
2019/01/03 HTML / CSS
HTML5的download属性详细介绍和使用实例
2014/04/23 HTML / CSS
大学活动总结范文
2014/04/29 职场文书
群众路线查摆问题及整改措施
2014/10/10 职场文书
委托公证书样本
2015/01/23 职场文书
自愿离婚协议书范本2016
2016/03/18 职场文书
妇联2016年六一国际儿童节活动总结
2016/04/06 职场文书
再见,2019我们不负使命;你好,2020我们砥砺前行
2020/01/03 职场文书
mysql查找连续出现n次以上的数字
2022/05/11 MySQL
DQL数据查询语句使用示例
2022/12/24 MySQL