web-dev-qa-db-ja.com

Meteor:クライアントからMongoコレクションへのファイルのアップロードvsファイルシステムvs GridFS

Meteorは優れていますが、従来のファイルのアップロードをネイティブでサポートしていません。ファイルのアップロードを処理するいくつかのオプションがあります。

クライアントから、次を使用してデータを送信できます。

  • Meteor.call( 'saveFile'、data)またはcollection.insert({file:data})
  • 'POST'フォームまたはHTTP.call( 'POST')

サーバー内、ファイルは次の場所に保存できます。

  • collection.insert({file:data})によるmongodbファイルコレクション
  • / path/to/dir内のファイルシステム
  • mongodb GridFS

これらの方法の長所と短所は何ですか?また、それらを実装する最善の方法は何ですか?サードパーティのサイトへの保存やURLの取得など、他のオプションもあることを認識しています。

37
Green

パッケージやサードパーティを使用せずに、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 File APIを使用して入力から取得されます。リーダーは、新しいFileReaderを使用して作成されます。ファイルはreadAsArrayBufferとして読み取られます。 console.logの場合、この配列バッファーは{}を返し、DDPはこれをネットワーク経由で送信できないため、Uint8Arrayに変換する必要があります。

これをMeteor.callに入れると、Meteorは自動的にEJSON.stringify(Uint8Array)を実行し、DDPで送信します。 chromeコンソールwebsocketトラフィックでデータを確認できます。base64に似た文字列が表示されます

サーバー側で、MeteorはEJSON.parse()を呼び出し、それをバッファに変換します

長所

  1. シンプルで、ハッキーな方法、余分なパッケージはありません
  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
});

説明

クライアント内のファイルが取得され、XHRオブジェクトが作成され、ファイルが「POST」経由でサーバーに送信されます。

サーバーでは、データは基礎となるファイルシステムにパイプされます。さらに、ファイル名を決定したり、サニタイズを実行したり、保存する前に既に存在するかどうかを確認したりできます。

長所

  1. XHR 2を利用して配列バッファーを送信できるため、オプション1と比較して新しいFileReader()は必要ありません
  2. 配列バッファはbase64文字列に比べてかさばらない
  3. サイズ制限なし、問題なくlocalhostで200 MBのファイルを送信しました
  4. ファイルシステムはmongodbよりも高速です(これについては、後のベンチマークで詳しく説明します)。
  5. キャッシュ可能およびgzip

短所

  1. XHR 2は古いブラウザでは使用できません。 IE10より下ですが、もちろん従来の投稿<form>を実装できます。Meteorの現在のHTTP.callはまだarraybufferを送信できないため、HTTP.call( 'POST')ではなくxhr = new XMLHttpRequest()のみを使用しました。 (私が間違っていれば私を指してください)。
  2. / path/to/dir /は流星の外側にある必要があります。そうでない場合、/ 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.x mongo_driver.js last lineによると、MongoInternalsというグローバルオブジェクトが公開されているため、defaultRemoteCollectionDriver()を呼び出して、GridStoreに必要な現在のデータベースdbオブジェクトを返すことができます。バージョンAでは、GridStoreもMongoInternalsによって公開されます。現在の流星で使用されるmongoはv1.4.xです

その後、ルート内で、var file = new GridStore(...)( [〜#〜] api [〜#〜] )を呼び出して、新しい書き込みオブジェクトを作成できます。次に、ファイルを開いてストリームを作成します。

また、バージョンBも含めました。このバージョンでは、Npm.require( 'mongodb')を介して新しいmongodbドライブを使用してGridStoreが呼び出されます。このmongoは、このドキュメントの執筆時点で最新のv2.0.13です。新しい [〜#〜] api [〜#〜] では、ファイルを開く必要はありません。stream(true)を直接呼び出してパイピングを開始できます

長所

  1. オプション2と同じ、arraybufferを使用して送信、オプション1のbase64文字列に比べてオーバーヘッドが少ない
  2. ファイル名のサニタイズについて心配する必要はありません
  3. ファイルシステムからの分離、一時ディレクトリへの書き込みの必要なし、データベースのバックアップ、rep、shardなど
  4. 他のパッケージを実装する必要はありません
  5. キャッシュ可能、gzip圧縮可能
  6. 通常のmongoコレクションに比べてはるかに大きなサイズを保存する
  7. パイプを使用してメモリの過負荷を軽減する

短所

  1. 不安定なMongo GridFS。バージョンA(mongo 1.x)とB(mongo 2.x)を含めました。バージョンAでは、10 MBを超える大きなファイルをパイピングすると、破損したファイル、未完成のパイプなど、多くのエラーが発生しました。この問題は、mongo 2.xを使用してバージョンBで解決されました。うまくいけば、流星は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はファイルシステムよりもはるかに低速です。

Benchmarkオプション2およびオプション3で確認できます。varstart = Date.now()を含め、endを書き込むときにconsole.log time inms、以下が結果です。デュアルコア、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

FSはGridFSよりもはるかに高速です。200MBのファイルの場合、GridFSを使用すると約80秒かかりますが、FSでは約1秒しかかかりません。SSDを試したことはありません。ただし、実際には、帯域幅によってファイルがクライアントからサーバーにストリーミングされる速度が決まり、200 MB /秒の転送速度を達成することは一般的ではありません。 GridFS)はより一般的です。

結論

決して包括的なものではありませんが、ニーズに最適なオプションを決定できます。

  • [〜#〜] ddp [〜#〜]は最も単純でコアMeteorの原則に準拠していますが、データはより大きく、転送中に圧縮できず、キャッシュできません。ただし、小さなファイルのみが必要な場合は、このオプションが適している場合があります。
  • XHRとファイルシステムの組み合わせは「伝統的な」方法です。安定したAPI、高速、「ストリーミング可能」、圧縮可能、キャッシュ可能(ETagなど)が、個別のフォルダーにある必要がある
  • XFSとGridFSを組み合わせることで、ファイルシステムが数を制限している場合、キャッシュセット可能なスケーラブルなrepセット、スケーラブル、非接触ファイルシステムdir、大きなファイル、および多くのファイルの利点を得ることができます。ただし、APIは不安定で、複数の書き込みでエラーが発生します。s..l..o..w..

うまくいけば、meteor DDPはgzip、キャッシングなどをサポートでき、GridFSはfaster...

74
Green

こんにちは、ファイルの表示に関してOption1に追加するだけです。私はejsonなしでやった。

<template name='tryUpload'>
  <p>Choose file to upload</p>
  <input name="upload" class='fileupload' type='file'>
</template>

Template.tryUpload.events({
'change .fileupload':function(event,template){
console.log('change & view');
var f = event.target.files[0];//assuming upload 1 file only
if(!f) return;
var r = new FileReader();
r.onload=function(event){
  var buffer = new Uint8Array(r.result);//convert to binary
  for (var i = 0, strLen = r.length; i < strLen; i++){
    buffer[i] = r.charCodeAt(i);
  }
  var toString = String.fromCharCode.apply(null, buffer );
  console.log(toString);
  //Meteor.call('saveFiles',buffer);
}
r.readAsArrayBuffer(f);};
0
bobobobooo