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学习笔记(五) Array 数组类型介绍
Jun 19 Javascript
jQuery 获取URL的GET参数值的小例子
Apr 18 Javascript
jquery ajax实现下拉框三级无刷新联动,且保存保持选中值状态
Oct 29 Javascript
js实现点击获取验证码倒计时效果
Jan 28 Javascript
微信小程序开发的四十个技术窍门总结(推荐)
Jan 23 Javascript
Vue.2.0.5实现Class 与 Style 绑定的实例
Jun 20 Javascript
vue2.0 axios前后端数据处理实例代码
Jun 30 Javascript
Mongoose实现虚拟字段查询的方法详解
Aug 15 Javascript
通过button将form表单的数据提交到action层的实例
Sep 08 Javascript
Vue组件之Tooltip的示例代码
Oct 18 Javascript
使用javascript做时间倒数读秒功能的实例
Jan 23 Javascript
JQuery属性操作与循环用法示例
May 15 jQuery
在小程序开发中使用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
玩家交还《星际争霸》原始码光盘 暴雪报以厚礼
2017/05/05 星际争霸
php array_walk_recursive 使用自定的函数处理数组中的每一个元素
2016/11/16 PHP
PHP递归遍历文件夹去除注释并压缩php源代码的方法示例
2018/05/23 PHP
laravel开发环境homestead搭建过程详解
2020/07/03 PHP
JavaScript的面向对象方法以及差别
2008/03/31 Javascript
jquery ui dialog ie8出现滚动条的解决方法
2010/12/06 Javascript
JavaScript中的字符串操作详解
2013/11/12 Javascript
jQuery动态改变图片显示大小(修改版)的实现思路及代码
2013/12/24 Javascript
浅谈javascript中for in 和 for each in的区别
2015/04/23 Javascript
使用AngularJS对路由进行安全性处理的方法
2015/06/18 Javascript
js实现图片放大和拖拽特效代码分享
2015/09/05 Javascript
javascript实现3D切换焦点图
2015/10/16 Javascript
基于Bootstrap实现图片轮播效果
2016/05/22 Javascript
JQuery和PHP结合实现动态进度条上传显示
2016/11/23 Javascript
jQuery remove()过滤被删除的元素(推荐)
2017/07/18 jQuery
AngularJS实现的输入框字数限制提醒功能示例
2017/10/26 Javascript
基于layui table返回的值的多级嵌套的解决方法
2019/09/19 Javascript
js实现三角形粒子运动
2020/09/22 Javascript
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
2021/01/05 Vue.js
解决谷歌搜索技术文章时打不开网页问题的python脚本
2013/02/10 Python
Python操作MongoDB数据库的方法示例
2018/01/04 Python
python3+requests接口自动化session操作方法
2018/10/13 Python
python使用numpy读取、保存txt数据的实例
2018/10/14 Python
浅析Python 3 字符串中的 STR 和 Bytes 有什么区别
2018/10/14 Python
详解Django定时任务模块设计与实践
2019/07/24 Python
北京-环亚运商测试题.net程序员初步测试题
2013/05/28 面试题
校园报刊亭创业计划书
2014/01/02 职场文书
《沙漠中的绿洲》教学反思
2014/04/24 职场文书
班级旅游计划书
2014/05/03 职场文书
建筑安全生产目标责任书
2014/07/23 职场文书
公司股东合作协议书
2014/09/14 职场文书
2014年个人工作总结模板
2014/12/15 职场文书
2015年社区重阳节活动总结
2015/07/30 职场文书
安全生产会议制度
2015/08/06 职场文书
Minikube搭建Kubernetes集群
2022/03/31 Servers
Grafana可视化监控系统结合SpringBoot使用
2022/04/19 Redis