您可以使用Meteor实现文件上传,而无需使用更多软件包或第三方
选项 1:DDP,将文件保存到 mongo 集合
/*** client.js ***/
// asign a change event into input tag
'change input' : function(event,template){
var file = event.target.files[0]; //assuming 1 file only
if (!file) return;
var reader = new FileReader(); //create a reader according to HTML5 File API
reader.onload = function(event){
var buffer = new Uint8Array(reader.result) // convert to binary
Meteor.call('saveFile', buffer);
}
reader.readAsArrayBuffer(file); //read the file as arraybuffer
}
/*** server.js ***/
Files = new Mongo.Collection('files');
Meteor.methods({
'saveFile': function(buffer){
Files.insert({data:buffer})
}
});
解释
首先,使用 HTML5 文件 API 从输入中获取文件。使用新的 FileReader 创建读取器。该文件被读取为 readAsArrayBuffer。如果您console.log,此数组缓冲区将返回{},并且DDP无法通过网络发送此数据,因此必须将其转换为Uint8Array。
当您将其放入 Meteor.call 中时,Meteor 会自动运行 EJSON.stringify(Uint8Array) 并使用 DDP 发送它。你可以在chrome控制台websocket流量中查看数据,你会看到类似base64的字符串
在服务器端,Meteor 调用 EJSON.parse() 并将其转换回缓冲区
Pros
- 简单,没有hacky方式,没有额外的包
- 坚持线上数据原则
Cons
- 更多带宽:生成的 Base64 字符串比原始文件大约 33%
- 文件大小限制:无法发送大文件(限制 ~ 16 MB?)
- 无缓存
- 还没有 gzip 或压缩
- 如果发布文件会占用大量内存
选项2:XHR,从客户端发布到文件系统
/*** client.js ***/
// asign a change event into input tag
'change input' : function(event,template){
var file = event.target.files[0];
if (!file) return;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/uploadSomeWhere', true);
xhr.onload = function(event){...}
xhr.send(file);
}
/*** server.js ***/
var fs = Npm.require('fs');
//using interal webapp or iron:router
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = fs.createWriteStream('/path/to/dir/filename');
file.on('error',function(error){...});
file.on('finish',function(){
res.writeHead(...)
res.end(); //end the respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file); //pipe the request to the file
});
解释
抓取客户端中的文件,创建 XHR 对象,然后通过“POST”将文件发送到服务器。
在服务器上,数据通过管道传输到底层文件系统中。您还可以在保存之前确定文件名、执行清理或检查文件是否已存在等。
Pros
- 利用 XHR 2,您可以发送 arraybuffer,与选项 1 相比,不需要新的 FileReader()
- 与 Base64 字符串相比,Arraybuffer 体积更小
- 没有大小限制,我在本地主机中发送了约 200 MB 的文件,没有任何问题
- 文件系统比 mongodb 更快(稍后将在下面的基准测试中详细介绍)
- 可缓存和 gzip
Cons
- XHR 2 在较旧的浏览器中不可用,例如IE10以下,但当然可以实现传统的 post
- /path/to/dir/ 必须位于 Meteor 之外,否则在 /public 中写入文件会触发重新加载
选项 3:XHR,保存到 GridFS
/*** client.js ***/
//same as option 2
/*** version A: server.js ***/
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = MongoInternals.NpmModule.GridStore;
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = new GridStore(db,'filename','w');
file.open(function(error,gs){
file.stream(true); //true will close the file automatically once piping finishes
file.on('error',function(e){...});
file.on('end',function(){
res.end(); //send end respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file);
});
});
/*** version B: server.js ***/
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var GridStore = Npm.require('mongodb').GridStore; //also need to add Npm.depends({mongodb:'2.0.13'}) in package.js
WebApp.connectHandlers.use('/uploadSomeWhere',function(req,res){
//var start = Date.now()
var file = new GridStore(db,'filename','w').stream(true); //start the stream
file.on('error',function(e){...});
file.on('end',function(){
res.end(); //send end respone
//console.log('Finish uploading, time taken: ' + Date.now() - start);
});
req.pipe(file);
});
解释
客户端脚本与选项 2 中的相同。
根据 Meteor 1.0.xmongo_driver.js https://github.com/meteor/meteor/blob/devel/packages/mongo/mongo_driver.js最后一行,暴露了一个名为 MongoInternals 的全局对象,您可以调用 defaultRemoteCollectionDriver() 返回 GridStore 所需的当前数据库 db 对象。在版本 A 中,GridStore 也由 MongoInternals 公开。目前meteor使用的mongo是v1.4.x
然后在路由中,您可以通过调用 var file = new GridStore(...) (API http://mongodb.github.io/node-mongodb-native/1.4/markdown-docs/gridfs.html)。然后,您打开该文件并创建一个流。
我还包含了版本 B。在此版本中,通过 Npm.require('mongodb') 使用新的 mongodb 驱动器调用 GridStore,此 mongo 是截至撰写本文时最新的 v2.0.13。新的API http://mongodb.github.io/node-mongodb-native/2.0/tutorials/gridfs/不需要你打开文件,你可以直接调用stream(true)并开始管道
Pros
- 与选项 2 相同,使用 arraybuffer 发送,与选项 1 中的 base64 字符串相比开销更少
- 无需担心文件名清理问题
- 与文件系统分离,无需写入临时目录,数据库可以备份、重复、分片等
- 无需实现任何其他包
- 可缓存且可压缩
- 与普通 mongo 集合相比,存储更大的尺寸
- 使用管道减少内存过载
Cons
-
不稳定的 Mongo GridFS。我包含版本 A (mongo 1.x) 和 B (mongo 2.x)。在版本 A 中,当管道大于 10 MB 的大文件时,我遇到了很多错误,包括文件损坏、管道未完成。这个问题在版本B中使用mongo 2.x解决了,希望meteor很快升级到mongodb 2.x
-
API混乱。在版本 A 中,您需要先打开文件才能进行流式传输,但在版本 B 中,您无需调用 open 即可进行流式传输。 API 文档也不是很清楚,并且流不能 100% 与 Npm.require('fs') 进行语法交换。在 fs 中,您调用 file.on('finish') 但在 GridFS 中,您在写入完成/结束时调用 file.on('end') 。
- GridFS不提供写入原子性,因此如果对同一个文件有多个并发写入,最终结果可能会有很大不同
-
Speed。 Mongo GridFS 比文件系统慢得多。
基准您可以在选项 2 和选项 3 中看到,我包含了 var start = Date.now() ,并且在写入 end 时,我 console.log 输出了时间ms,下面是结果。双核、4 GB 内存、硬盘、基于 ubuntu 14.04。
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
可以看到FS比GridFS快很多。对于 200 MB 的文件,使用 GridFS 大约需要 80 秒,但在 FS 中只需要大约 1 秒。我没有尝试过SSD,结果可能会有所不同。然而,在现实生活中,带宽可能决定文件从客户端传输到服务器的速度,达到 200 MB/秒的传输速度并不常见。另一方面,传输速度约为 2 MB/秒 (GridFS) 更为常见。
结论
这绝不是全面的,但您可以决定哪个选项最适合您的需求。
-
DDP是最简单的,遵循 Meteor 的核心原理,但数据体积更大,传输过程中不可压缩,不可缓存。但如果您只需要小文件,此选项可能会很好。
-
XHR 与文件系统结合是“传统”方式。稳定的 API、快速、“可流式传输”、可压缩、可缓存(ETag 等),但需要位于单独的文件夹中
-
XHR 与 GridFS 结合,您将获得重复集、可扩展、不接触文件系统目录、大文件和许多文件(如果文件系统限制数量)的好处,还可以缓存可压缩。然而,API 不稳定,多次写入都会出错,s..l..o..w..
希望很快,meteor DDP 可以支持 gzip、缓存等,GridFS 也可以faster...