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 YUI 读码日记之 YAHOO.util.Dom - Part.4
Mar 22 Javascript
JavaScript获取路径设计源码
May 22 Javascript
用JavaScript实现用一个DIV来包装文本元素节点
Sep 09 Javascript
JavaScript清空数组元素的两种方法简单比较
Jul 10 Javascript
JS实现超精简响应鼠标显示二级菜单代码
Sep 12 Javascript
jQuery插件passwordStrength密码强度指标详解
Jun 24 Javascript
使用JS实现图片展示瀑布流效果的实例代码
Sep 12 Javascript
Bootstrap学习笔记 轮播(Carousel)插件
Mar 21 Javascript
跨域解决之JSONP和CORS的详细介绍
Nov 21 Javascript
jQuery实现的点击图片居中放大缩小功能示例
Jan 16 jQuery
JS在Array数组中按指定位置删除或添加元素对象方法示例
Nov 19 Javascript
vue+element ui实现锚点定位
Jun 29 Vue.js
在小程序开发中使用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/13 PHP
CodeIgniter针对数据库的连接、配置及使用方法
2016/03/03 PHP
让ThinkPHP的模板引擎达到最佳效率的方法详解
2017/03/14 PHP
PHP数据分析引擎计算余弦相似度算法示例
2017/08/08 PHP
js调用webservice中的方法实现思路及代码
2013/02/25 Javascript
JS命名空间的另一种实现
2013/08/09 Javascript
jQuery 无限级菜单的简单实例
2014/02/21 Javascript
谈谈基于iframe、FormData、FileReader三种无刷新上传文件的方法
2015/12/03 Javascript
Vue.js实现拖放效果的实例
2016/09/30 Javascript
Bootstrap选项卡动态切换效果
2016/11/28 Javascript
javascript读取文本节点方法小结
2016/12/15 Javascript
30分钟快速实现小程序语音识别功能
2018/11/27 Javascript
javascript实现超好看的3D烟花特效
2020/01/01 Javascript
[49:05]OG vs Newbee 2019DOTA2国际邀请赛淘汰赛 胜者组 BO3 第二场 8.21.mp4
2020/07/19 DOTA
使用Python神器对付12306变态验证码
2016/01/05 Python
通过Python使用saltstack生成服务器资产清单
2016/03/01 Python
python微信跳一跳系列之色块轮廓定位棋盘
2018/02/26 Python
取numpy数组的某几行某几列方法
2018/04/03 Python
详解pyenv下使用python matplotlib模块的问题解决
2018/11/29 Python
python随机在一张图像上截取任意大小图片的方法
2019/01/24 Python
python中树与树的表示知识点总结
2019/09/14 Python
基于python的BP神经网络及异或实现过程解析
2019/09/30 Python
Python利用多线程同步锁实现多窗口订票系统(推荐)
2019/12/22 Python
pycharm sciview的图片另存为操作
2020/06/01 Python
python利用递归方法实现求集合的幂集
2020/09/07 Python
python链表类中获取元素实例方法
2021/02/23 Python
canvas 下载二维码和图片加水印的方法
2018/03/21 HTML / CSS
写给女朋友的道歉信
2014/01/08 职场文书
职业生涯规划书前言
2014/04/15 职场文书
缓刑人员思想汇报500字
2014/09/12 职场文书
结婚司仪主持词
2015/06/29 职场文书
聘任书格式及范文
2015/09/21 职场文书
资产移交协议书
2016/03/24 职场文书
Pytorch实现图像识别之数字识别(附详细注释)
2021/05/11 Python
JavaScript异步操作中串行和并行
2021/11/20 Javascript
python+opencv实现目标跟踪过程
2022/06/21 Python