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 相关文章推荐
javascript function调用时的参数检测常用办法
Feb 26 Javascript
一个报数游戏js版(约瑟夫环问题)
Aug 05 Javascript
javascript 中的 delete及delete运算符
Nov 15 Javascript
JS验证逗号隔开可以是中文字母数字
Apr 22 Javascript
BOM系列第二篇之定时器requestAnimationFrame
Aug 17 Javascript
AngulerJS学习之按需动态加载文件
Feb 13 Javascript
canvas雪花效果核心代码分享
Feb 19 Javascript
详解vue移动端项目代码拆分记录
Mar 15 Javascript
JS中call()和apply()的功能及用法实例分析
Jun 28 Javascript
vue vantUI tab切换时 list组件不触发load事件的问题及解决方法
Feb 14 Javascript
ant-design-vue中tree增删改的操作方法
Nov 03 Javascript
基于Vant UI框架实现时间段选择器
Dec 24 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站内搜索关键词变亮的实现方法
2014/12/30 PHP
1亿条数据如何分表100张到Mysql数据库中(PHP)
2015/07/29 PHP
Yii数据模型中rules类验证器用法分析
2016/07/15 PHP
laradock环境docker-compose操作详解
2019/07/29 PHP
yii2.0框架多模型操作示例【添加/修改/删除】
2020/04/13 PHP
Javascript刷新窗口方法小结
2015/10/21 Javascript
怎么引入(调用)一个JS文件
2016/05/26 Javascript
浅谈javascript运算符——条件,逗号,赋值,()和void运算符
2016/07/15 Javascript
深入理解JS中的Function.prototype.bind()方法
2016/10/11 Javascript
js for循环倒序输出数组元素的实例
2017/03/01 Javascript
JavaScript实现省市县三级级联特效
2017/05/16 Javascript
彻底解决 webpack 打包文件体积过大问题
2017/07/07 Javascript
js排序与重组的实例讲解
2017/08/28 Javascript
详解vue2.0 不同屏幕适配及px与rem转换问题
2018/02/23 Javascript
详解vue组件开发脚手架
2018/06/15 Javascript
Vue兼容ie9的问题全面解决方案
2018/06/19 Javascript
使用VueCli3+TypeScript+Vuex一步步构建todoList的方法
2019/07/25 Javascript
nuxt框架中对vuex进行模块化设置的实现方法
2019/09/06 Javascript
使用js实现一个简单的滚动条过程解析
2019/09/10 Javascript
[49:07]VGJ.T vs Optic Supermajor小组赛D组 BO3 第二场 6.3
2018/06/04 DOTA
使用Python编写一个模仿CPU工作的程序
2015/04/16 Python
关于Django外键赋值问题详解
2017/08/13 Python
python之pymysql模块简单应用示例代码
2019/12/16 Python
使用Python制作缩放自如的圣诞老人(圣诞树)
2019/12/25 Python
Python 使用office365邮箱的示例
2020/10/29 Python
逼真的HTML5树叶飘落动画
2016/03/01 HTML / CSS
Speedo速比涛中国官方网站:全球领先泳装运动品牌
2018/04/24 全球购物
区域总监的岗位职责
2013/11/21 职场文书
会议邀请书范文
2014/02/02 职场文书
干部选拔任用方案
2014/05/26 职场文书
抗震救灾标语
2014/06/26 职场文书
2014年卫生监督工作总结
2014/12/09 职场文书
具结保证书
2015/01/17 职场文书
人代会简报
2015/07/21 职场文书
java基础——多线程
2021/07/03 Java/Android
MySQL事务操作的四大特性以及并发事务问题
2022/04/12 MySQL