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 冒号 使用说明
Jun 06 Javascript
热点新闻滚动特效的js代码
Aug 17 Javascript
ECMAScript6新增值比较函数Object.is
Jun 12 Javascript
轻松学习Javascript闭包函数
Dec 15 Javascript
javascript对象的创建和访问
Mar 08 Javascript
使用node.js中的Buffer类处理二进制数据的方法
Nov 26 Javascript
原生js实现简单的Ripple按钮实例代码
Mar 24 Javascript
vue.js父组件使用外部对象的方法示例
Apr 25 Javascript
详解Vue demo实现商品列表的展示
May 07 Javascript
微信小程序实现消息框弹出动画
Apr 18 Javascript
详解react组件通讯方式(多种)
May 06 Javascript
一道JS算法面试题——冒泡、选择排序
Apr 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实现memcache缓存示例讲解
2013/12/04 PHP
WampServer下安装多个版本的PHP、mysql、apache图文教程
2015/01/07 PHP
PHP之密码加密的几种方式
2015/07/29 PHP
php利用header函数下载各种文件
2016/08/24 PHP
PHP二进制与字符串之间的相互转换教程
2016/10/14 PHP
解决php-fpm.service not found问题的办法
2017/06/06 PHP
PHP实现十进制数字与二十六进制字母串相互转换操作示例
2018/08/10 PHP
jQuery使用手册之二 DOM操作
2007/03/24 Javascript
在JavaScript中通过URL传递汉字的方法
2007/04/09 Javascript
客户端限制只能上传jpg格式图片的js代码
2010/12/09 Javascript
html文件中jquery与velocity变量中的$冲突的解决方法
2013/11/01 Javascript
js 判断上传文件大小及格式代码
2013/11/13 Javascript
由浅入深讲解Javascript继承机制与simple-inheritance源码分析
2015/12/13 Javascript
js实现的下拉框二级联动效果
2016/04/30 Javascript
基于jquery插件实现拖拽删除图片功能
2020/08/27 Javascript
checkbox批量选中,获取选中项的值的简单实例
2016/06/28 Javascript
通过jquery实现页面的动画效果(实例代码)
2016/09/18 Javascript
jQuery animate()实现背景色渐变效果的处理方法【使用jQuery.color.js插件】
2017/03/15 Javascript
如何使用Bootstrap创建表单
2017/03/29 Javascript
mui框架 页面无法滚动的解决方法(推荐)
2018/01/25 Javascript
微信小程序实现下拉菜单切换效果
2020/03/30 Javascript
express express-session的使用小结
2018/12/12 Javascript
js实现鼠标拖拽缩放div实例代码
2019/03/25 Javascript
python文件操作之批量修改文件后缀名的方法
2018/08/10 Python
对Python3 * 和 ** 运算符详解
2019/02/16 Python
解决Tensorboard可视化错误:不显示数据 No scalar data was found
2020/02/15 Python
说出ArrayList,Vector, LinkedList的存储性能和特性
2015/01/04 面试题
通信工程专业女生个人求职信
2013/09/21 职场文书
会计毕业生求职简历的自我评价
2013/10/20 职场文书
人力资源管理毕业生自荐信
2013/11/21 职场文书
高校教师自荐信范文
2014/03/13 职场文书
请假条怎么写
2014/04/10 职场文书
有关环保的标语
2014/06/13 职场文书
2015年幼儿园班主任工作总结
2015/05/12 职场文书
思品教学工作总结
2015/08/10 职场文书
十二月早安励志心语大全
2019/12/03 职场文书