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 相关文章推荐
firefox和IE系列的相关区别整理 以备后用
Dec 28 Javascript
js操作select控件的几种方法
Jun 02 Javascript
javascript获取网页中指定节点的父节点、子节点的方法小结
Apr 24 Javascript
JQuery中关于jquery.js与jquery.min.js的比较探讨
May 15 Javascript
js文件缓存之版本管理详解
Jul 05 Javascript
jquery在项目中做复选框时遇到的一些问题笔记
Nov 17 Javascript
jQuery判断一个元素是否可见的方法
Jun 05 Javascript
Vue中fragment.js使用方法详解
Mar 09 Javascript
Node调用Java的示例代码
Sep 20 Javascript
jquery.picsign图片标注组件实例详解
Feb 02 jQuery
如何解决React官方脚手架不支持Less的问题(小结)
Sep 12 Javascript
详解element-ui 表单校验 Rules 配置 常用黑科技
Jul 11 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安装threads多线程扩展基础教程
2015/11/17 PHP
PHP简单实现防止SQL注入的方法
2018/03/13 PHP
laravel框架实现敏感词汇过滤功能示例
2020/02/15 PHP
Jquery 滑入滑出效果实现代码
2010/03/27 Javascript
jQuery学习笔记 获取jQuery对象
2012/09/19 Javascript
借助script进行Http跨域请求:JSONP实现原理及代码
2013/03/19 Javascript
去掉gridPanel表头全选框的小例子
2013/07/18 Javascript
引入JS文件IE6报语法错误或缺少对象问题的解决方法
2014/01/09 Javascript
利用JavaScript阻止表单提交的两种方法
2016/08/11 Javascript
jquery实现超简单的瀑布流布局【推荐】
2017/03/08 Javascript
JS组件系列之JS组件封装过程详解
2017/04/28 Javascript
Vue中父组件向子组件通信的方法
2017/07/11 Javascript
React+react-dropzone+node.js实现图片上传的示例代码
2017/08/23 Javascript
Vue实现美团app的影院推荐选座功能【推荐】
2018/08/29 Javascript
浅谈vue 单文件探索
2018/09/05 Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
2018/11/13 Javascript
vue 翻页组件vue-flip-page效果
2020/02/05 Javascript
[03:08]迎霜节狂欢!2018年迎霜节珍藏Ⅰ一览
2018/12/25 DOTA
Python使用百度API上传文件到百度网盘代码分享
2014/11/08 Python
对numpy中数组转置的求解以及向量内积计算方法
2018/10/31 Python
python Pandas如何对数据集随机抽样
2019/07/29 Python
django 数据库返回queryset实现封装为字典
2020/05/19 Python
Python新手学习raise用法
2020/06/03 Python
button在IE6/7下的黑边去除方案
2012/12/24 HTML / CSS
AmazeUI图片轮播效果的示例代码
2020/08/20 HTML / CSS
StubHub新加坡:购买和出售全球活动门票
2017/03/10 全球购物
英国异国风情旅游网站:Travel Talk Tours(团体旅游、探险旅游、帆船假期)
2018/07/26 全球购物
请解释接口的显式实现有什么意义
2012/05/26 面试题
社区学习十八大感想
2014/01/22 职场文书
部门年终奖分配方案
2014/05/07 职场文书
社区领导班子四风问题原因分析及整改措施
2014/09/28 职场文书
党员组织生活会发言材料
2014/10/17 职场文书
首都博物馆观后感
2015/06/05 职场文书
担保公司2015年终工作总结
2015/10/14 职场文书
你会写报告?产品体验报告到底该怎么写?
2019/08/14 职场文书
matplotlib画混淆矩阵与正确率曲线的实例代码
2021/06/01 Python