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 相关文章推荐
25个好玩的JavaScript小游戏分享
Apr 22 Javascript
jQuery scroll事件实现监控滚动条分页示例
Apr 04 Javascript
使用AngularJS编写较为优美的JavaScript代码指南
Jun 19 Javascript
javascript实现添加附件功能的方法
Nov 18 Javascript
Bootstrap选项卡与Masonry插件的完美结合
Jul 06 Javascript
如何利用模板将HTML从JavaScript中抽离
Oct 08 Javascript
jQuery 插件封装的方法
Nov 16 Javascript
5分钟打造简易高效的webpack常用配置
Jul 04 Javascript
利用pm2部署多个node.js项目的配置教程
Oct 22 Javascript
详解如何将 Vue-cli 改造成支持多页面的 history 模式
Nov 20 Javascript
判断iOS、Android以及PC端的示例代码
Nov 15 Javascript
微信小程序中限制激励式视频广告位显示次数(实现思路)
Dec 06 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性能优化的介绍
2013/06/20 PHP
php使用fgetcsv读取csv文件出现乱码的解决方法
2014/11/08 PHP
php+mysql删除指定编号员工信息的方法
2015/01/14 PHP
thinkphp下MySQL数据库读写分离代码剖析
2017/04/18 PHP
javascript控制frame,iframe的src属性代码
2009/12/31 Javascript
jquery.hotkeys监听键盘按下事件keydown插件
2014/05/11 Javascript
用队列模拟jquery的动画算法实例
2015/01/20 Javascript
ECMAScript 5严格模式(Strict Mode)介绍
2015/03/02 Javascript
javascript事件冒泡实例分析
2015/05/13 Javascript
JavaScript中toString()方法的使用详解
2015/06/05 Javascript
最新最热最实用的15个jQuery插件汇总
2015/07/05 Javascript
Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信
2017/02/23 Javascript
Angular实现的table表格排序功能完整示例
2017/12/22 Javascript
基于vue如何发布一个npm包的方法步骤
2019/05/15 Javascript
javascript实现一款好看的秒表计时器
2020/09/05 Javascript
vue中jsonp插件的使用方法示例
2020/09/10 Javascript
Vue组件生命周期运行原理解析
2020/11/25 Vue.js
微信小程序选择图片控件
2021/01/19 Javascript
在nodejs中创建child process的方法
2021/01/26 NodeJs
[00:12]2018DOTA2亚洲邀请赛 sylar表现SOLO技艺
2018/04/06 DOTA
python复制文件的方法实例详解
2015/05/22 Python
Python实现的概率分布运算操作示例
2017/08/14 Python
python中学习K-Means和图片压缩
2017/11/20 Python
Python实现爬虫爬取NBA数据功能示例
2018/05/28 Python
Python中pandas dataframe删除一行或一列:drop函数详解
2018/07/03 Python
python如何生成网页验证码
2018/07/28 Python
python 定时器,实现每天凌晨3点执行的方法
2019/02/20 Python
一文读懂Python 枚举
2020/08/25 Python
美国修容界大佬创建的个人美妆品牌:Kevyn Aucoin Beauty
2018/12/12 全球购物
银行求职信范文
2014/05/26 职场文书
秋季运动会演讲稿
2014/09/16 职场文书
pytorch实现线性回归以及多元回归
2021/04/11 Python
基于python制作简易版学生信息管理系统
2021/04/20 Python
详解python字符串驻留技术
2021/05/21 Python
Mysql超详细讲解死锁问题的理解
2022/04/01 MySQL
Spring Cloud OpenFeign模版化客户端
2022/06/25 Java/Android