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开发框架小成 学习js的朋友可以看看
Nov 16 Javascript
juqery 学习之五 文档处理 插入
Feb 11 Javascript
js弹出模式对话框,并接收回传值的方法
Mar 12 Javascript
js网页版计算器的简单实现
Jul 02 Javascript
js脚本实现数据去重
Nov 27 Javascript
禁用backspace网页回退功能的实现代码
Nov 15 Javascript
js实现的xml对象转json功能示例
Dec 24 Javascript
js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标
Jan 04 Javascript
vue3.0 CLI - 2.4 - 新组件 Forms.vue 中学习表单
Sep 14 Javascript
详解JavaScript中的强制类型转换
Apr 15 Javascript
Vue Router 实现动态路由和常见问题及解决方法
Mar 06 Javascript
JS创建自定义对象的六种方法总结
Dec 15 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
PHP4(windows版本)中的COM函数
2006/10/09 PHP
ThinkPHP实现ajax仿官网搜索功能实例
2014/12/02 PHP
ThinkPHP实现支付宝接口功能实例
2014/12/02 PHP
PHP常用处理静态操作类
2015/04/03 PHP
Joomla实现组件中弹出一个模式(modal)窗口的方法
2016/05/04 PHP
PHP实现数组array转换成xml的方法
2016/07/19 PHP
Laravel 集成 Geetest验证码的方法
2018/05/14 PHP
Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件)
2011/06/27 Javascript
浅析jQuery中常用的元素查找方法总结
2013/07/04 Javascript
javascript获取选中的文本的方法代码
2013/10/30 Javascript
实现51Map地图接口(示例代码)
2013/11/22 Javascript
jquery easyui 结合jsp简单展现table数据示例
2014/04/18 Javascript
javascript基础知识分享之类与函数化
2016/02/13 Javascript
纯jQuery实现前端分页功能
2017/03/23 jQuery
jQuery ajax读取本地json文件的实例
2017/10/31 jQuery
VUE + UEditor 单图片跨域上传功能的实现方法
2018/02/08 Javascript
webpack 4.0.0-beta.0版本新特性介绍
2018/02/10 Javascript
elementUI 动态生成几行几列的方法示例
2019/07/11 Javascript
html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】
2019/09/10 jQuery
[44:10]2018DOTA2亚洲邀请赛 4.5 淘汰赛 EG vs VP 第一场
2018/04/06 DOTA
Python天气预报采集器实现代码(网页爬虫)
2012/10/07 Python
pandas Dataframe行列读取的实例
2018/06/08 Python
浅谈tensorflow 中tf.concat()的使用
2020/02/07 Python
python中文分词库jieba使用方法详解
2020/02/11 Python
GDAL 矢量属性数据修改方式(python)
2020/03/10 Python
Python Serial串口基本操作(收发数据)
2020/11/06 Python
DBA数据库管理员JAVA程序员架构师必看
2016/02/07 面试题
临床医学应届生求职信
2013/11/06 职场文书
学校综治宣传月活动总结
2014/07/02 职场文书
招商引资工作汇报材料
2014/10/28 职场文书
环卫工作汇报材料
2014/10/28 职场文书
2014年调度员工作总结
2014/11/19 职场文书
2015年煤矿安全工作总结
2015/05/23 职场文书
2015年小学美术工作总结
2015/05/25 职场文书
小学六年级班主任工作经验交流材料
2015/11/02 职场文书
MySQL数据库超时设置配置的方法实例
2021/10/15 MySQL