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 Global对象
Aug 13 Javascript
ExtJs 表单提交登陆实现代码
Aug 19 Javascript
jQuery 菜单随滚条改为以定位方式(固定要浏览器顶部)
May 24 Javascript
jquery插件jquery倒计时插件分享
Dec 27 Javascript
javascript实现表格增删改操作实例详解
May 15 Javascript
js实现可键盘控制的简单抽奖程序
Jul 13 Javascript
JavaScript定义数组的三种方法(new Array(),new Array('x','y')
Oct 04 Javascript
vue.js与element-ui实现菜单树形结构的解决方法
Apr 21 Javascript
vuejs选中当前样式active的实例
Aug 22 Javascript
VUE组件中的 Drawer 抽屉实现代码
Aug 06 Javascript
微信小程序中weui用法解析
Oct 21 Javascript
OpenLayers3实现图层控件功能
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语法(3)
2006/10/09 PHP
图书管理程序(二)
2006/10/09 PHP
在WordPress中实现发送http请求的相关函数解析
2015/12/29 PHP
Symfony2安装第三方Bundles实例详解
2016/02/04 PHP
PHP实现的进度条效果详解
2016/05/03 PHP
Laravel中错误与异常处理的用法示例
2018/09/16 PHP
document.getElementById为空或不是对象的解决方法
2010/01/24 Javascript
js报错 Object doesn't support this property or method的原因分析
2011/03/31 Javascript
JQuery获取浏览器窗口内容部分高度的代码
2012/02/24 Javascript
jquery实现鼠标拖拽滑动效果来选择数字的方法
2015/05/04 Javascript
JavaScript点击按钮后弹出透明浮动层的方法
2015/05/11 Javascript
JavaScript运算符小结
2015/06/03 Javascript
关于RequireJS的简单介绍即使用方法
2016/10/20 Javascript
自适应布局meta标签中viewport、content、width、initial-scale、minimum-scale、maximum-scale总结
2017/08/18 Javascript
axios携带cookie配置详解(axios+koa)
2018/12/28 Javascript
详解JavaScript中new操作符的解析和实现
2020/09/04 Javascript
微信小程序淘宝首页双排图片布局排版代码(推荐)
2020/10/29 Javascript
[44:50]2018DOTA2亚洲邀请赛 4.1 小组赛 A组 TNC vs VG
2018/04/02 DOTA
使用Python来编写HTTP服务器的超级指南
2016/02/18 Python
python实现指定字符串补全空格、前面填充0的方法
2018/11/16 Python
Python创建字典的八种方式
2019/02/27 Python
python实现计算器功能
2019/10/31 Python
Django之choices选项和富文本编辑器的使用详解
2020/04/01 Python
python3通过udp实现组播数据的发送和接收操作
2020/05/05 Python
对Keras中predict()方法和predict_classes()方法的区别说明
2020/06/09 Python
html5应用缓存_动力节点Java学院整理
2017/07/13 HTML / CSS
html5适合移动应用开发的12大特性
2014/03/19 HTML / CSS
洲际酒店集团大中华区:IHG中国
2016/08/17 全球购物
松本清官方海外旗舰店:日本最大的药妆连锁店
2017/11/21 全球购物
DJI美国:消费类无人机领域的领导者
2018/04/27 全球购物
2014年社区植树节活动方案
2014/02/28 职场文书
增员口号大全
2014/06/18 职场文书
升学宴答谢词
2015/01/05 职场文书
2015年学校党支部工作总结
2015/04/01 职场文书
2016社区平安家庭事迹材料
2016/02/26 职场文书
Python Matplotlib绘制动画的代码详解
2022/05/30 Python