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 MD4
Dec 20 Javascript
jQuery写的日历(包括日历的样式及功能)
Apr 23 Javascript
使用Node.js实现一个简单的FastCGI服务器实例
Jun 09 Javascript
jQuery操作JSON的CRUD用法实例
Feb 25 Javascript
javascript动态创建表格及添加数据实例详解
May 13 Javascript
JS模拟简易滚动条效果代码(附demo源码)
Apr 05 Javascript
Javascript中this绑定的3种方法与比较
Oct 13 Javascript
jquery+Jscex打造游戏力度条
Sep 12 Javascript
基于JavaScript实现的插入排序算法分析
Apr 14 Javascript
javascript流程控制语句集合
Sep 18 Javascript
eslint 的三大通用规则详解
May 16 Javascript
JS FormData对象使用方法实例详解
Feb 12 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用SAX解析XML的实现代码与问题分析
2011/08/22 PHP
PHP file_exists问题杂谈
2012/05/07 PHP
基于PHP 面向对象之成员方法详解
2013/05/04 PHP
destoon会员注册提示“数据校验失败(2)”解决方法
2014/06/21 PHP
php格式输出文件var_export函数实例
2014/11/15 PHP
一个可以显示阴历的JS代码
2007/03/05 Javascript
jquery 图片截取工具jquery.imagecropper.js
2010/04/09 Javascript
游览器中javascript的执行过程(图文)
2012/05/20 Javascript
AMD异步模块定义介绍和Require.js中使用jQuery及jQuery插件的方法
2014/06/06 Javascript
Javascript实现计算个人所得税
2015/05/10 Javascript
jQuery获取上传文件的名称的正则表达式
2015/05/21 Javascript
浅谈jQuery效果函数
2016/09/16 Javascript
Bootstrap 下拉多选框插件Bootstrap Multiselect
2017/01/22 Javascript
微信小程序 基础知识css样式media标签
2017/02/15 Javascript
ionic+AngularJs实现获取验证码倒计时按钮
2017/04/22 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
2017/10/23 Javascript
SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题的解决方法
2018/01/09 Javascript
Vue Cli3 创建项目的方法步骤
2018/10/15 Javascript
Vue CLI2升级至Vue CLI3的方法步骤
2019/05/20 Javascript
layui的布局和表格的渲染以及动态生成表格的方法
2019/09/18 Javascript
[01:57]2018年度DOTA2最具潜力解说-完美盛典
2018/12/16 DOTA
[58:59]完美世界DOTA2联赛PWL S3 access vs CPG 第一场 12.13
2020/12/16 DOTA
python科学计算之scipy——optimize用法
2019/11/25 Python
Python tkinter实现图片标注功能(完整代码)
2019/12/08 Python
Pytorch实现基于CharRNN的文本分类与生成示例
2020/01/08 Python
TensorFlow加载模型时出错的解决方式
2020/02/06 Python
联想韩国官网:Lenovo Korea
2018/05/10 全球购物
Hotels.com拉丁美洲:从豪华酒店到经济型酒店的预定优惠和折扣
2019/12/09 全球购物
一些高难度的SQL面试题
2016/11/29 面试题
药学专业大专生的自我评价
2013/12/12 职场文书
开办饭店创业计划书
2013/12/28 职场文书
军训自我鉴定怎么写
2014/02/13 职场文书
民警群众路线教育实践活动对照检查材料
2014/10/04 职场文书
2014年党总支工作总结
2014/12/18 职场文书
法律意见书范文
2015/05/20 职场文书
tensorboard 可视化之localhost:6006不显示的解决方案
2021/05/22 Python