Meteor:将文件从客户端上传到Mongo集合vs文件系统与GridFS

Gre*_*een 38 file-upload mongodb gridfs meteor

Meteor很棒,但它缺乏传统文件上传的原生支持.有几种方法可以处理文件上传:

从客户端,可以使用以下方式发送数据:

  • Meteor.call('saveFile',data)或collection.insert({file:data})
  • 'POST'表单或HTTP.call('POST')

在服务器中,文件可以保存到:

  • collection.insert({file:data})的mongodb文件集合
  • 文件系统在/ path/to/dir中
  • mongodb GridFS

这些方法的优缺点是什么以及如何最好地实现它们?我知道还有其他选项,例如保存到第三方网站并获取网址.

Gre*_*een 77

您可以使用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})         
    }   
});
Run Code Online (Sandbox Code Playgroud)

Explantion

首先,使用HTML5 File API从输入中获取文件.使用新的FileReader创建阅读器.该文件读作readAsArrayBuffer.这个arraybuffer,如果你是console.log,返回{}并且DDP不能通过网络发送它,所以它必须转换为Uint8Array.

当你把它放在Meteor.call中时,Meteor自动运行EJSON.stringify(Uint8Array)并用DDP发送它.您可以检查chrome console websocket流量中的数据,您将看到类似base64的字符串

在服务器端,Meteor调用EJSON.parse()并将其转换回缓冲区

优点

  1. 简单,没有hacky方式,没有额外的包
  2. 坚持有线数据原则

缺点

  1. 更多带宽:生成的base64字符串比原始文件大约33%
  2. 文件大小限制:无法发送大文件(限制~16 MB?)
  3. 没有缓存
  4. 还没有gzip或压缩
  5. 如果发布文件,请占用大量内存

选项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
});
Run Code Online (Sandbox Code Playgroud)

说明

抓取客户端中的文件,创建XHR对象,并通过"POST"将文件发送到服务器.

在服务器上,数据通过管道传输到底层文件系统.您还可以在保存之前确定文件名,执行消毒或检查是否已存在等.

优点

  1. 利用XHR 2可以发送arraybuffer,与选项1相比,不需要新的FileReader()
  2. 与base64字符串相比,Arraybuffer不那么笨重
  3. 没有大小限制,我在localhost中发送了一个~200 MB的文件没有问题
  4. 文件系统比mongodb更快(稍后在下面的基准测试中更多)
  5. Cachable和gzip

缺点

  1. XHR 2在旧版浏览器中不可用,例如IE10以下,但当然你可以实现传统的帖子<form>我只使用xhr = new XMLHttpRequest(),而不是HTTP.call('POST')因为当前的HTTP.在Meteor中调用还不能发送arraybuffer(如果我错了就指出我).
  2. / 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);
});     
Run Code Online (Sandbox Code Playgroud)

说明

客户端脚本与选项2中的相同.

根据Meteor 1.0.x mongo_driver.js的最后一行,公开了一个名为MongoInternals的全局对象,您可以调用defaultRemoteCollectionDriver()来返回GridStore所需的当前数据库db对象.在版本A中,MongoInternals也公开了GridStore.当前流星使用的mongo是v1.4.x.

然后在路由中,您可以通过调用var file = new GridStore(...)(API)来创建新的写入对象.然后,您打开该文件并创建一个流.

我还包括一个版本B.在这个版本中,通过Npm.require('mongodb')使用新的mongodb驱动器调用GridStore,这个mongo是撰写本文时最新的v2.0.13.新的API不需要您打开文件,您可以直接调用stream(true)并开始管道

优点

  1. 与使用arraybuffer发送的选项2相同,与选项1中的base64字符串相比,开销更少
  2. 无需担心文件名清除
  3. 与文件系统分离,无需写入temp dir,db可以备份,rep,shard等
  4. 无需实施任何其他包
  5. Cachable并且可以进行gzip压缩
  6. 与普通的mongo系列相比,存放更大的尺寸
  7. 使用管道减少内存过载

缺点

  1. 不稳定的Mongo GridFS.我包括版本A(mongo 1.x)和B(mongo 2.x).在版本A中,当管理大于10 MB的大文件时,我收到了很多错误,包括损坏的文件,未完成的管道.使用mongo 2.x在版本B中解决了这个问题,希望meteor很快升级到mongodb 2.x.
  2. API混淆.在版本A中,您需要先打开文件才能进行流式传输,但在版本B中,您可以在不调用open的情况下进行流式传输.API文档也不是很清楚,并且流与Npm.require('fs')不是100%语法可交换的.在fs中,你调用file.on('finish')但在GridFS中你在写完成/结束时调用file.on('end').
  3. GridFS不提供写入原子性,因此如果同一文件有多个并发写入,则最终结果可能会有很大差异
  4. 速度.Mongo GridFS比文件系统慢得多.

基准测试 您可以在选项2和选项3中看到,我包括var start = Date.now(),当写入结束时,我在console.log中输出以ms为单位的时间,下面是结果.双核,4 GB RAM,HDD,ubuntu 14.04.

file size   GridFS  FS
100 KB      50      2
1 MB        400     30
10 MB       3500    100
200 MB      80000   1240
Run Code Online (Sandbox Code Playgroud)

您可以看到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 ..

希望很快,流星DDP可以支持gzip,缓存等,GridFS可以更快 ......