[Node.js] 使用File API 异步上传文件
原文地址:http://www.moye.me/2014/11/05/html5-filereader/
最近在做一个网盘的项目,不出意外的涉及到大文件的上传,那么问题来了:如何实时的显示文件上传的进度?
问题分解
似乎是老生常谈,几年前我做过类似的功能模块(基于.NET平台),方案思路:
- 基于表单提交
- Server端根据上传文件分配标识符(GUID)并进行流式读取
- Browser端发起Ajax拉取文件上传状态
这种方案的问题是受制于文件大小(最大2G)。所谓文件上传进度的实时显示,个人觉得比较理想的方案是:
- Browser 端需要告诉Server文件的大小
- Browser 端需要能对文件分块读取
- Server 端需要根据接收到的块及文件大小计算出进度,并告知Browser端
- Browser 端在进度未完成时,继续读取分块上传
HTML5 File API
上述方案中,最大的难点在于Browser端分块读取文件。好在HTML5 File API提供了这样的接口:FileReader
使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要读取的文件或数据。其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象……
有意思的是Blob接口,它只有一个方法:slice()——不难想象,它是用进行数据分块的,方法签名形如:
Blob slice(
optional long long start,
optional long long end,
optional DOMString contentType
};
从W3C Draft 可以看出,File 接口实际上是继承自Blob接口的,意味着File.slice(start, end) 可以返回文件的块数据,结合FileReader.readAsBinaryString方法,我们在Browser端能读取到本地文件的任意部分数据。
关于FileReader
首先,FileReader并不是每个浏览器都支持的,兼容性测试情况(很不幸,巨硬的IE又拖后腿了……:
| 操作系统 | Firefox | Chrome | Internet Explorer | Opera | Safari |
| Windows | 支持 | 支持 | 不支持 | 支持 | 不支持 |
| MAC OS X | 支持 | 支持 | N/A | 支持 | 支持 |
其次,使用readAsBinaryString的方法,需要对FileReader的 onloadend事件进行订阅处理,即读取块数据操作完成时,这个事件订阅方法将得到已读取的二进制块数据:
currentFileReader.onload = function (evnt) {
console.log('Data content length: ', evnt.target.result.length);
};
B/S通信
拿到了块数据,剩下的问题是怎么发出去,有这么些选项:AJAX,富客户端编程,WebSocket。由于网盘项目基于Node开发,我选用了Socket.IO 做为B/S两端通信的框架。
从B端开始
页面准备和引用:
<div>
<progress id="progressBar" value="0" max="100"></progress>
</div>
<input type="button" id="choose-button" value="选择文件">
<input type="file" id="choose-file" class="hidden"/>
</div>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
浏览器兼容性测试先行:
if (!window.File && !window.FileReader) {
alert('Your browser does not support the File API. Please use modern browser');
return;
} else {
var socket = io.connect();
var currentFile = null;
var currentFileReader = null;
}
在用户选择了文件后,对相应事件进行处理:
$('#choose-file').on('change', function () {
currentFile = document.getElementById('choose-file').files[0];
if (currentFile) {
currentFileReader = new FileReader();
currentFileReader.onload = function (evnt) {
socket.emit('upload', {
'Name': currentFile.name,
'Segment': evnt.target.result
});
};
socket.emit('start', {
'Name': currentFile.name,
'Size': currentFile.size
});
}
});
从上边的代码可以看出,socket.emit('start') 是整个交互流程的开始,它告诉Server端文件信息;FileReader.onload 则按块向Server端 emit 数据。还缺一段触发 FileReader的代码:
socket.on('moreData', function (data) {
updateProgressBar(data.percent);
var position = data.position * 524288;
var newFile = null;
if (currentFile.slice)
newFile = currentFile.slice(position, position + Math.min(524288, currentFile.size - position));
else if (currentFile.webkitSlice)
newFile = currentFile.webkitSlice(position, position + Math.min(524288, currentFile.size - position));
else if (currentFile.mozSlice)
newFile = currentFile.mozSlice(position, position + Math.min(524288, currentFile.size - position));
if (newFile)
currentFileReader.readAsBinaryString(newFile); // trigger upload event
});
Browser端这个moreData消息,是由Server端触发的,在收到start消息后,Server端将向Browser端发送这个moreData消息。这里需要注意的是,各家浏览器对于Blob.slice接口实现不一 (Firefox 12之前的版本上为blob.mozSlice(), Safari上为blob.webkitSlice()
上传完成的收尾工作:
socket.on('done', function (data) {
delete currentFileReader;
delete currentFile;
updateProgressBar(100);
});
Server端实现
首先,需要一个全局数据结构,来保存每一个上传文件的描述符(传完后从作用域删除):
var Files = {};
然后是Socket.IO的初始化,准备文件描述符:
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
//prepare for uploading
socket.on('start', function (data) {
var name = data.Name;
var size = data.Size;
var filePath = '/tmp';
var position = 0;
Files[name] = { // define storage structure
fileSize: size,
data: '',
downloaded: 0,
handler: null,
filePath: filePath,
};
Files[name].getPercent = function () {
return parseInt((this.downloaded / this.fileSize) * 100);
};
Files[name].getPosition = function () {
return this.downloaded / 524288;
};
fs.open(Files[name].filePath, 'a', 0755, function (err, fd) {
if (err)
console.log('[start] file open error: ' + err.toString());
else {
Files[name].handler = fd; // the file descriptor
socket.emit('moreData', { 'position': position, 'percent': 0 });
}
});
});
});
Server端收到upload消息时,并不立即写入,而是进行缓冲,以10M分批进行写入:
socket.on('upload', function (data) {
var name = data.Name;
var segment = data.Segment;
Files[name].downloaded += segment.length;
Files[name].data += segment;
if (Files[name].downloaded === Files[name].fileSize) {
fs.write(Files[name].handler, Files[name].data, null, 'Binary',
function (err, written) {
//uploading completed
delete Files[name];
socket.emit('done', { file: file });
});
} else if (Files[name].data.length > 10485760) { //buffer >= 10MB
fs.write(Files[name].handler, Files[name].data, null, 'Binary',
function (err, Writen) {
Files[name].data = ''; //reset the buffer
socket.emit('moreData', {
'position': Files[name].getPosition(),
'percent': Files[name].getPercent()
});
});
}
else {
socket.emit('moreData', {
'position': Files[name].getPosition(),
'percent': Files[name].getPercent()
});
}
});
小结
基于File API 上传方案最大的问题是兼容性,IE,你懂的… 不过,时代总是在进步,我们不能被腐朽落后绑架而裹足不前,也没准开发者和用户的力量真能让这些腐朽落后的玩意儿淡出我们的视线
更多文章请移步我的blog新地址: http://www.moye.me/
[Node.js] 使用File API 异步上传文件的更多相关文章
- Node.js——异步上传文件
前台代码 submit() { var file = this.$refs.fileUpload.files[0]; var formData = new FormData(); formData.a ...
- 利用ajaxfileupload.js异步上传文件
1.引入ajaxfileupload.js 2.html代码 <input type="file" id="enclosure" name="e ...
- JS异步上传文件
直接调用Upload(option)方法,即可上传文件,不需要额外的插件辅助,采用原生js编写. /* *异步上传文件 *option参数 **url:上传路径 **data:上传的其他数据{id:& ...
- 关于js异步上传文件
好久没登录博客园了,今天来一发分享. 最近项目里有个需求,上传文件(好吧,这种需求很常见,这也不是第一次遇到了).当时第一想法就是直接用form表单提交(原谅我以前就是这么干的),不过表单里不仅有文件 ...
- 利用jquery.form实现异步上传文件
实现原理 目前需要在一个页面实现多个地方调用上传控件上传文件,并且必须是异步上传.思考半天,想到通过创建动态表单包裹上传文件域,利用jquery.form实现异步提交表单,从而达到异步上传的目的,在上 ...
- Servlet异步上传文件
这里需要用到插件ajaxfileupload.js,jar包:commons-fileupload-1.3.2.jar,commons-io-2.5.jar 注意红色部分的字!!!! 1.创建一个we ...
- struts2 jquery ajaxFileUpload 异步上传文件
网上搜集的,整理一下. 一.ajaxFileUpload 实现异步上传文件利用到了ajaxFileUpload.js这个文件,这是别人开发的一个jquery的插件,可以实现文件的上传并能够和strut ...
- 【转】JQuery插件ajaxFileUpload 异步上传文件(PHP版)
前几天想在手机端做个异步上传图片的功能,平时用的比较多的JQuery图片上传插件是Uploadify这个插件,效果很不错,但是由于手机不支持flash,所以不得不再找一个文件上传插件来用了.后来发现a ...
- 异步上传文件,ajax上传文件,jQuery插件之ajaxFileUpload
http://www.cnblogs.com/kissdodog/archive/2012/12/15/2819025.html 一.ajaxFileUpload是一个异步上传文件的jQuery插件. ...
随机推荐
- TableView didSelectRowAtIndexPath 不执行
1.父类事件设置代理 UIGestureRecognizer *tapGesture ... tapGesture.delegate = self; 2.覆盖方法 - (BOOL)gestureRe ...
- iOS:界面适配(三)--iPhone不同机型适配 6/6plus 前
转:http://blog.csdn.net/houseq/article/details/40051207 对于不同苹果设备,各个参数查看<iOS:机型参数.sdk.xcode各版本>. ...
- SQL SERVER 2008中输入汉字乱码的问题
搭建服务器时,系统是英文版windows server 2008 ,安装的中文语言包.安装SqlServer2008 后,数据库中文显示乱码. baidu 后,说是 排序规则 的问题.修改为 Chin ...
- 解决POI读取Excel如何判断行是不是为空
在作Excel表导入数据库的时候要统计成功导入了多少条,失败了多少条. 问题一:Excel表里有225行,只有3行是有数据的,但是我在读Excel表的时候它连没有数据的行也读进来了. 问题二:如果你是 ...
- Docker实践(1)—入门
tutorial centos6.5环境. # yum install docker-io -y 会依赖安装libcgroup,lxc,lxc-libs 启动docker # service dock ...
- 【实用技巧】取消Win7开机账户的手动选择
因为前面碰到的一些事情,稍有感慨. 关于win7的一些小技巧都不是什么很有技术含量东西,或者说很浅显.我说一个技巧,也许很多人都知道,也许也早有人说过.但我想说的是我不是在炫耀什么,我只是想分享一些我 ...
- 线程互斥与析构函数中mutex的销毁
正在实现一个线程池的pthread包装器,突然发现有人在讨论关于http://blog.csdn.net/Solstice/article/details/5238671 是一篇比较老的文章,考虑了下 ...
- 机器学习编程语言之争,Python 夺魁【转载+整理】
原文地址 en cn 本文内容 表现平平的 MATLAB 貌似强大的 Julia 本身无错的 R 语言 逐渐没落的 Perl 老而弥坚的 Python 我个人很喜欢 Python~ 随着科技的发展,拥 ...
- Thinkpad X240使用U盘安装Win7系统
更改BIOS设置 不同电脑的进入BIOS的方式可能不太一样,Thinkpad X240的进入方式是在电脑启动的时候按下回车键,然后按F1进入BIOS. 1. 修改secure boot为Disable ...
- [iOS Xcode8]上传AppStore 无法构建版本 没有➕号
最近iOS10出来了 Xcode也跟着升级到了8 想着App做个更新 于是修改好了代码打算上传新包 ,无奈总是发现构建不了新版本 经过各种蛋疼的查找.我列一下我的经验 1.如果是收费的App,那么是要 ...