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实现阻止iOS APP中的链接打开Safari浏览器
Jun 12 Javascript
jQuery下拉美化搜索表单效果代码分享
Aug 25 Javascript
window.setInterval()方法的定义和用法及offsetLeft与style.left的区别
Nov 11 Javascript
利用BootStrap弹出二级对话框的简单实现方法
Sep 21 Javascript
利用jQuery插件imgAreaSelect实现获得选择域的图像信息
Dec 02 Javascript
jQuery实现联动下拉列表查询框
Jan 04 Javascript
VUE实现表单元素双向绑定(总结)
Aug 08 Javascript
使用Vue组件实现一个简单弹窗效果
Apr 23 Javascript
详解JavaScript的变量
Apr 04 Javascript
javascript事件监听与事件委托实例详解
Aug 16 Javascript
JS面试题中深拷贝的实现讲解
May 07 Javascript
javascript canvas时钟模拟器
Jul 13 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扩展编写点滴 技巧收集
2010/03/09 PHP
DedeCMS dede_channeltype表字段注释
2010/04/07 PHP
destoon常用的安全设置概述
2014/06/21 PHP
php中数据库连接方式pdo和mysqli对比分析
2015/02/25 PHP
jQuery中$.each使用详解
2015/01/29 Javascript
JavaScript中用于生成随机数的Math.random()方法
2015/06/15 Javascript
js实现瀑布流的三种方式比较
2020/06/28 Javascript
JavaScript从数组的indexOf()深入之Object的Property机制
2016/05/11 Javascript
Vue 2.0+Vue-router构建一个简单的单页应用(附源码)
2017/03/14 Javascript
jquery实现图片放大点击切换
2017/06/06 jQuery
JavaScript 通过Ajax 动态加载CheckBox复选框
2017/08/31 Javascript
Angular4学习笔记之根模块与Ng模块
2017/09/09 Javascript
jQuery选择器之基本过滤选择器用法实例分析
2019/02/19 jQuery
nodejs分离html文件里面的js和css的方法
2019/04/09 NodeJs
Nodejs + Websocket 指定发送及群聊的实现
2020/01/09 NodeJs
[04:54]DOTA2-DPC中国联赛1月31日Recap集锦
2021/03/11 DOTA
Python中的pprint折腾记
2015/01/21 Python
Python通过正则表达式选取callback的方法
2015/07/18 Python
Python IDLE 错误:IDLE''s subprocess didn''t make connection 的解决方案
2017/02/13 Python
详解 Python中LEGB和闭包及装饰器
2017/08/03 Python
Python读取文件内容的三种常用方式及效率比较
2017/10/07 Python
快速解决pandas.read_csv()乱码的问题
2018/06/15 Python
python+opencv实现高斯平滑滤波
2020/07/21 Python
python3实现指定目录下文件sha256及文件大小统计
2019/02/25 Python
Python socket模块ftp传输文件过程解析
2019/11/05 Python
PyCharm GUI界面开发和exe文件生成的实现
2020/03/04 Python
利用python如何实现猫捉老鼠小游戏
2020/12/04 Python
解释一下Windows的消息机制
2014/01/30 面试题
厨师岗位职责
2013/11/12 职场文书
高三政治教学反思
2014/02/06 职场文书
家具促销活动方案
2014/02/16 职场文书
工商企业管理应届生求职信
2014/05/04 职场文书
项目工作说明书
2014/07/29 职场文书
抗洪救灾感谢信
2015/01/22 职场文书
副校长2015年教育教学工作总结
2015/07/27 职场文书
Python中的matplotlib绘制百分比堆叠柱状图,并为每一个类别设置不同的填充图案
2022/04/20 Python