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 事件参考手册
Dec 24 Javascript
为jQuery.Treeview添加右键菜单的实现代码
Oct 22 Javascript
50个比较实用jQuery代码段
Sep 18 Javascript
js简单实现用户注册信息的校验代码
Nov 15 Javascript
Jquery ajaxStart()与ajaxStop()方法(实例讲解)
Dec 18 Javascript
对js中回调函数的一些看法
Aug 29 Javascript
浅谈javascript的闭包
Jan 23 Javascript
树结构之JavaScript
Jan 24 Javascript
微信小程序实现动态改变view标签宽度和高度的方法【附demo源码下载】
Dec 05 Javascript
代码详解Vuejs响应式原理
Dec 20 Javascript
解决Vue开发中对话框被遮罩层挡住的问题
Nov 26 Javascript
js实现unicode码字符串与utf8字节数据互转详解
Mar 21 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实现mysql数据库备份类
2008/03/20 PHP
php中在PDO中使用事务(Transaction)
2011/05/14 PHP
PHP的简易冒泡法代码分享
2012/08/28 PHP
javascript 对表格的行和列都能加亮显示
2008/12/26 Javascript
javascript document.compatMode兼容性
2010/02/23 Javascript
jquery $(&quot;#variable&quot;) 循环改变variable的值示例
2014/02/23 Javascript
使用jQuery异步加载 JavaScript脚本解决方案
2014/04/20 Javascript
AngularJS表单编辑提交功能实例
2015/02/13 Javascript
jQuery中 prop() attr()使用详解
2015/05/19 Javascript
JavaScript实现多栏目切换效果
2016/12/12 Javascript
easyui-edatagrid.js实现回车键结束编辑功能的实例
2017/04/12 Javascript
AngularJS双向绑定和依赖反转实例详解
2017/04/15 Javascript
jQuery tip提示插件(实例分享)
2017/04/28 jQuery
socket.io与pm2(cluster)集群搭配的解决方案
2017/06/02 Javascript
Vue组件选项props实例详解
2017/08/18 Javascript
react项目实践之webpack-dev-serve
2018/09/14 Javascript
vue-cli在 history模式下的配置详解
2019/11/26 Javascript
解决vue的router组件component在import时不能使用变量问题
2020/07/26 Javascript
antd的select下拉框因为数据量太大造成卡顿的解决方式
2020/10/31 Javascript
python 判断一个进程是否存在
2009/04/09 Python
Python单例模式的两种实现方法
2017/08/14 Python
利用selenium 3.7和python3添加cookie模拟登陆的实现
2017/11/20 Python
Python之Scrapy爬虫框架安装及简单使用详解
2017/12/22 Python
解决Pycharm出现的部分快捷键无效问题
2018/10/22 Python
Python线上环境使用日志的及配置文件
2019/07/28 Python
python numpy 常用随机数的产生方法的实现
2019/08/21 Python
python导入库的具体方法
2020/06/18 Python
python中执行smtplib失败的处理方法
2020/07/01 Python
HTML5 canvas画矩形时出现边框样式不一致的解决方法
2013/10/14 HTML / CSS
社区母亲节活动记录
2014/03/06 职场文书
小学教师培训方案
2014/06/09 职场文书
管辖权异议上诉状
2015/05/23 职场文书
中秋节英文祝福语句(14句)
2019/09/11 职场文书
Python 中 Shutil 模块详情
2021/11/11 Python
剖析后OpLog订阅MongoDB的数据变更就没那么难了
2022/02/24 MongoDB
Python if else条件语句形式详解
2022/03/24 Python