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格式化时间和js格式化时间戳示例
Feb 10 Javascript
JS实现方向键切换输入框焦点的方法
Aug 19 Javascript
JavaScript的Backbone.js框架的一些使用建议整理
Feb 14 Javascript
JavaScript中的boolean布尔值使用学习及相关技巧讲解
May 26 Javascript
jQuery UI Grid 模态框中的表格实例代码
Apr 01 jQuery
详解Layer弹出层样式
Aug 21 Javascript
Javascript中toFixed计算错误(依赖银行家舍入法的缺陷)解决方法
Aug 22 Javascript
Cocos2d实现刮刮卡效果
Dec 20 Javascript
vue-cli 项目打包完成后运行文件路径报错问题
Jul 19 Javascript
解决vue.js提交数组时出现数组下标的问题
Nov 05 Javascript
javascript实现扫雷简易版
Aug 18 Javascript
Openlayers实现测量功能
Sep 25 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数组函数序列之rsort() - 对数组的元素值进行降序排序
2011/11/02 PHP
如何在smarty中增加类似foreach的功能自动加载数据
2013/06/26 PHP
PHP生成不同颜色、不同大小的tag标签函数
2013/09/23 PHP
php实现12306火车票余票查询和价格查询(12306火车票查询)
2014/01/14 PHP
memcache一致性hash的php实现方法
2015/03/05 PHP
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
2010/06/18 Javascript
IE与FireFox中的childNodes区别
2011/10/20 Javascript
使用UglifyJS合并/压缩JavaScript的方法
2012/03/07 Javascript
JavaScript window.document的属性、方法和事件小结
2012/10/24 Javascript
js 判断checkbox是否选中的操作方法
2012/11/09 Javascript
js获取事件源及触发该事件的对象
2013/10/24 Javascript
JS+CSS实现简易实用的滑动门菜单效果
2015/09/18 Javascript
JS+HTML5实现的前端购物车功能插件实例【附demo源码下载】
2016/10/17 Javascript
angularjs实现天气预报功能
2020/06/16 Javascript
Vue+Flask实现简单的登录验证跳转的示例代码
2018/01/13 Javascript
JavaScript设计模式之装饰者模式实例详解
2019/01/17 Javascript
vue input实现点击按钮文字增删功能示例
2019/01/29 Javascript
解决Vue调用springboot接口403跨域问题
2019/09/02 Javascript
vue下axios拦截器token刷新机制的实例代码
2020/01/17 Javascript
Python re模块介绍
2014/11/30 Python
Python+Pika+RabbitMQ环境部署及实现工作队列的实例教程
2016/06/29 Python
Python实现多态、协议和鸭子类型的代码详解
2019/05/05 Python
python实现差分隐私Laplace机制详解
2019/11/25 Python
利用Python将多张图片合成视频的实现
2020/11/23 Python
CSS3实现的炫酷菜单代码分享
2015/03/12 HTML / CSS
10种CSS3实现的loading动画,挑一个走吧?
2020/11/16 HTML / CSS
H&M旗下高端女装品牌:& Other Stories
2018/05/07 全球购物
法国二手MacBook销售网站:Okamac
2019/03/18 全球购物
开发中都用到了那些设计模式?用在什么场合?
2014/08/21 面试题
中专三年学习的个人自我评价
2013/12/12 职场文书
最新大学生创业计划书写作攻略
2014/04/02 职场文书
2015年超市员工工作总结
2015/05/04 职场文书
行政申诉状范文
2015/05/20 职场文书
工作后的感想
2015/08/07 职场文书
2016婚礼主持词开场白
2015/11/24 职场文书
Axios代理配置及封装响应拦截处理方式
2022/04/07 Vue.js