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 相关文章推荐
jquery mobile实现拨打电话功能的几种方法
Aug 05 Javascript
设置checkbox为只读(readOnly)的两种方式
Oct 11 Javascript
JavaScript中对象介绍
Dec 31 Javascript
JavaScript中的lastIndexOf()方法使用详解
Jun 06 Javascript
jQuery插件datatables使用教程
Apr 21 Javascript
使用three.js 画渐变的直线
Jun 05 Javascript
Javascript单例模式的介绍和实例
Oct 08 Javascript
浅谈js中同名函数和同名变量的执行问题
Feb 12 Javascript
微信小程序 本地数据读取实例
Apr 27 Javascript
jQuery Ajax 实现分页 kkpager插件实例代码
Aug 10 jQuery
react 创建单例组件的方法
Apr 26 Javascript
LayUI表格批量删除方法
Aug 15 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对字符串的递增运算分析
2010/08/08 PHP
php使用curl和正则表达式抓取网页数据示例
2014/04/13 PHP
php实现约瑟夫问题的方法小结
2015/03/23 PHP
标准版Eclipse搭建PHP环境的详细步骤
2015/11/18 PHP
php制作简单模版引擎
2016/04/07 PHP
JS获取IUSR_机器名和IWAM_机器名帐号的密码
2006/12/06 Javascript
JavaScript prototype 使用介绍
2013/08/29 Javascript
window.open()详解及浏览器兼容性问题示例探讨
2014/05/29 Javascript
jQuery简易图片放大特效示例代码
2014/06/09 Javascript
jQuery简单实现日历的方法
2015/05/04 Javascript
ng-options和ng-checked在表单中的高级运用(推荐)
2017/01/21 Javascript
node.js基于mongodb的搜索分页示例
2017/01/22 Javascript
想用好React的你必须要知道的一些事情
2017/07/24 Javascript
javascript 中事件冒泡和事件捕获机制的详解
2017/09/01 Javascript
微信小程序template模板与component组件的区别和使用详解
2019/05/22 Javascript
Vant picker 多级联动操作
2020/11/02 Javascript
python 多线程应用介绍
2012/12/19 Python
Python写入CSV文件的方法
2015/07/08 Python
Python处理PDF及生成多层PDF实例代码
2017/04/24 Python
python中MethodType方法介绍与使用示例
2017/08/03 Python
Python实现将MySQL数据库表中的数据导出生成csv格式文件的方法
2018/01/11 Python
详解pyqt5 动画在QThread线程中无法运行问题
2018/05/05 Python
pandas的唯一值、值计数以及成员资格的示例
2018/07/25 Python
Python进程间通信 multiProcessing Queue队列实现详解
2019/09/23 Python
解决安装新版PyQt5、PyQT5-tool后打不开并Designer.exe提示no Qt platform plugin的问题
2020/04/24 Python
pandas dataframe 中的explode函数用法详解
2020/05/18 Python
python中yield的用法详解
2021/01/13 Python
html5中为audio标签增加停止按钮动作实现方法
2013/01/04 HTML / CSS
使用分层画布来优化HTML5渲染的教程
2015/05/08 HTML / CSS
Roxy荷兰官方网站:冲浪、滑雪板、服装和配件
2019/10/22 全球购物
党员党性分析材料
2014/02/17 职场文书
销售顾问岗位职责
2014/02/25 职场文书
留学生求职信
2014/06/03 职场文书
数控专业毕业生求职信
2014/06/12 职场文书
庆祝教师节演讲稿
2014/09/03 职场文书
2015年高校教师个人工作总结
2015/05/25 职场文书