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 相关文章推荐
一个js控制的导航菜单实例代码
Dec 03 Javascript
js中精确计算加法和减法示例
Mar 28 Javascript
js控制href内容的连接内容的变化示例
Apr 30 Javascript
分享一个常用的javascript静态类
Dec 31 Javascript
实例详解JSON数据格式及json格式数据域字符串相互转换
Jan 07 Javascript
jQuery on()绑定动态元素出现的问题小结
Feb 19 Javascript
js实现目录链接,内容跟着目录滚动显示的简单实例
Oct 15 Javascript
js获取浏览器的各种属性
Apr 27 Javascript
Node.js中的不安全跳转如何防御详解
Oct 21 Javascript
全面了解JavaScript的作用域链
Apr 03 Javascript
详解基于原生JS验证表单组件xy-form
Aug 20 Javascript
jquery 键盘事件 keypress() keydown() keyup()用法总结
Oct 23 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
php和mysql中uft-8中文编码乱码的几种解决办法
2012/04/19 PHP
利用php绘制饼状图的实现代码
2013/06/07 PHP
PHP扩展开发入门教程
2015/02/26 PHP
自制PHP框架之模型与数据库
2017/05/07 PHP
filemanage功能中用到的lib.js
2007/04/08 Javascript
js实现的真正的iframe高度自适应(兼容IE,FF,Opera)
2010/03/07 Javascript
javascript 嵌套的函数(作用域链)
2010/03/15 Javascript
分享XmlHttpRequest调用Webservice的一点心得
2012/07/20 Javascript
jQuery extend 的简单实例
2013/09/18 Javascript
JS图片切换的具体方法(带缩略图版)
2013/11/12 Javascript
关于js数组去重的问题小结
2014/01/24 Javascript
简单方法判断JavaScript对象为null或者属性为空
2014/09/26 Javascript
JQuery选择器绑定事件及修改内容的方法
2015/01/23 Javascript
jQuery背景插件backstretch使用指南
2015/04/21 Javascript
基于JavaScript实现瀑布流效果(循环渐近)
2016/01/27 Javascript
微信小程序基于slider组件动态修改标签透明度的方法示例
2017/12/04 Javascript
js实现3D旋转相册
2020/08/02 Javascript
node.js通过url读取文件
2020/10/16 Javascript
[01:28:43]2014 DOTA2华西杯精英邀请赛5 24 DK VS CIS
2014/05/25 DOTA
[55:25]2018DOTA2亚洲邀请赛3月29日 小组赛A组 VG VS OG
2018/03/30 DOTA
[43:03]LGD vs Newbee 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
Python中3种内建数据结构:列表、元组和字典
2014/11/30 Python
Django的URLconf中使用缺省视图参数的方法
2015/07/18 Python
Android应用开发中Action bar编写的入门教程
2016/02/26 Python
Python连接SQLServer2000的方法详解
2017/04/19 Python
用pickle存储Python的原生对象方法
2017/04/28 Python
python处理Excel xlrd的简单使用
2017/09/12 Python
对python中array.sum(axis=?)的用法介绍
2018/06/28 Python
Python pandas DataFrame操作的实现代码
2019/06/21 Python
Python常用数据类型之间的转换总结
2019/09/06 Python
在Python中画图(基于Jupyter notebook的魔法函数)
2019/10/28 Python
python基于event实现线程间通信控制
2020/01/13 Python
pytorch快速搭建神经网络_Sequential操作
2020/06/17 Python
python解包概念及实例
2021/02/17 Python
武汉东之林科技有限公司机试
2013/09/17 面试题
Python基础学习之奇异的GUI对话框
2021/05/27 Python