Meteor: téléchargement de fichiers du client vers la collection Mongo vs système de fichiers vs GridFS

Meteor est génial mais il manque de supports natifs pour le téléchargement de fichiers traditionnels. Il y a plusieurs options pour gérer le téléchargement de fichiers:

à Partir du client, les données peuvent être envoyées en utilisant:

  • Meteor.appel ('saveFile', data) ou collecte.insert({fichier:données})
  • 'POST' form or HTTP.call ('POST')

dans le serveur, le fichier peut être enregistré pour:

  • une collection de fichiers mongodb par collection.insert({fichier:données})
  • système de fichiers dans /chemin/vers/dir
  • mongodb GridFS

Quels sont les avantages et les inconvénients de ces méthodes et comment les mettre en oeuvre? Je suis conscient qu'il existe également d'autres options telles que la sauvegarde sur un site tiers et l'obtention d'une url.

35
demandé sur Green 2015-01-14 03:50:23

2 réponses

vous pouvez obtenir le téléchargement de fichier tout simplement avec Meteor sans utiliser plus de paquets ou un tiers

Option 1: DDP, save file to a mongo collection

/*** 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})         
    }   
});

explanation

tout d'abord, le fichier est saisi à partir de l'entrée en utilisant L'API fichier HTML5. Un lecteur est créé en utilisant New FileReader. Le fichier est lu comme readAsArrayBuffer. Cette arraybuffer, si vous console.journal, renvoie {} et de la DDP ne pouvez pas envoyer cela sur le fil, de sorte qu'il a à convertir en Uint8Array.

quand vous mettez ça dans Meteor.appel, Meteor lance automatiquement EJSON.stringify (Uint8Array) et l'envoie avec DDP. Vous pouvez vérifier les données dans le trafic WebSocket de la console chrome, vous verrez une chaîne ressemblant à base64

côté serveur, Appel météore EJSON.parse() et la convertit en arrière pour le tampon

Pros

  1. Simple, pas de hacky façon, pas de paquets supplémentaires
  2. S'en tenir aux données sur le Fil de principe

Cons

  1. plus de bande passante: la chaîne de caractères base64 résultante est ~ 33% plus grande que le fichier original
  2. Taille du fichier limite: ne peut pas envoyer de gros fichiers (limite ~ 16 MB?)
  3. Pas de mise en cache
  4. Pas de la compression gzip ou encore
  5. Prendre beaucoup de mémoire si vous publiez des fichiers

Option 2: XHR, poste de client de système de fichiers

/*** 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
});

Explication

le fichier du client est saisi, un objet XHR est créé et le fichier est envoyé via 'POST' au serveur.

Sur le serveur, les données sont transmises dans un système de fichiers sous-jacent. Vous pouvez en outre déterminer le nom du fichier, effectuer l'assainissement ou vérifier s'il existe déjà etc avant de sauvegarder.

Pros

  1. profitez de XHR 2 pour envoyer arraybuffer, pas de new FileReader() est nécessaire par rapport à l'option 1
  2. Arraybuffer est moins encombrant par rapport à base64 de la chaîne
  3. Pas de limite de taille, j'ai envoyé un fichier ~ 200 MO en localhost avec pas de problème
  4. système de Fichiers est plus rapide que mongodb (plus de cela plus tard dans l'analyse comparative ci-dessous)
  5. Cachable et gzip

Cons

  1. XHR 2 n'est pas disponible dans les navigateurs plus anciens, par exemple en dessous de IE10, mais de bien sûr, vous pouvez implémenter un post traditionnel
    j'ai seulement utilisé xhr = new XMLHttpRequest (), plutôt que HTTP.call ('POST') parce que le HTTP courant.call in Meteor n'est pas encore en mesure d'envoyer arraybuffer (pointez-moi si je me trompe).
  2. /chemin/vers/dir/ a à l'extérieur de meteor, sinon l'écriture d'un fichier dans /public déclenche un reload

Option 3: XHR, save to 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);
});     

Explication

le client script est le même que dans l'option 2.

selon Meteor 1.0.x mongo_driver.js dernière ligne, un objet global appelé MongoInternals est exposé, vous pouvez appeler defaultRemoteCollectionDriver () pour retourner l'objet db de la base de données courante qui est requis pour le GridStore. Dans la version A, le GridStore est également exposé par les MongoInternals. Le mongo utilisé par le météore actuel est v1.4.x

alors à l'intérieur d'une route, vous pouvez créer un nouvel objet write en appelant le fichier var = new GridStore(...) (API). Vous ouvrez alors le fichier et créez un flux.

j'ai aussi inclus une version B. Dans cette version, le GridStore est appelé en utilisant un nouveau lecteur mongodb via Npm.require ('mongodb'), ce mongo est le dernier v2.0.13 au moment de la rédaction du présent rapport. La nouvelle API ne vous oblige pas à ouvrir le fichier, vous pouvez appeler stream(true) directement et commencer piping

Pros

  1. comme dans l'option 2, envoyé en utilisant arraybuffer, moins de overhead comparé à la chaîne base64 dans l'option 1
  2. Pas besoin de s'inquiéter à propos de fichier nom de suppression
  3. Séparation de système de fichiers, pas besoin d'écrire à la température dir, la db peuvent être sauvegardés, rep, tesson etc
  4. Pas besoin de mettre en œuvre n'importe quel autre paquet
  5. Cachable " et peut être au format gzip
  6. stocker des tailles beaucoup plus grandes par rapport à la collection normale de mongo
  7. utilisation d'un tuyau pour réduire la surcharge de la mémoire

Cons

  1. Unstable Mongo GridFS. J'ai inclus la version A (mongo 1.x) et B (mongo 2.x). Dans la version A, quand piping gros fichiers > 10 Mo, j'ai eu beaucoup d'erreurs, y compris fichier corrompu, tuyau inachevé. Ce problème est résolu dans la version B en utilisant mongo 2.X, avec un peu de chance, meteor passera à mongodb 2.x bientôt
  2. API confusion. Dans la version A, vous devez ouvrir le fichier avant de pouvoir stream, mais dans la version B, vous pouvez stream sans appeler open. Le doc de L'API n'est pas non plus très clair et le flux n'est pas 100% syntaxe échangeable avec Npm.require ('fs'). En SF, vous appelez file.("fini"), mais dans GridFS vous appelez fichier.('fin') lors de l'écriture de finitions/fin.
  3. GridFS ne fournit pas l'atomicité d'écriture, donc s'il y a plusieurs Écritures simultanées dans le même fichier, le résultat final peut être très différent
  4. Vitesse. Mongo GridFS est beaucoup plus lent que le système de fichiers.

Test Vous pouvez voir dans l'option 2 et l'option 3, j'ai inclus var start = Date.maintenant () et quand j'écris fin, je console.journal le temps à ms, ci-dessous le résultat. Dual Core, 4 Go de ram, HDD, ubuntu 14.04 basé.

file size   GridFS  FS
100 KB      50      2
1 MB        400     30
10 MB       3500    100
200 MB      80000   1240

vous pouvez voir que FS est beaucoup plus rapide que GridFS. Pour un fichier de 200 Mo, il prend ~ 80 sec en utilisant GridFS mais seulement ~ 1 sec en FS. Je n'ai pas essayé le SSD, le résultat peut être différent. Cependant, dans la vie réelle, la bande passante peut dicter la vitesse à laquelle le fichier est redirigé d'un client vers un serveur, l'atteinte de la vitesse de transfert de 200 Mo/sec n'est pas typique. D'un autre côté, une vitesse de transfert ~2 MB/sec (GridFS) est plus la norme.

Conclusion

ce n'est en aucun cas exhaustif, mais vous pouvez décider quelle option convient le mieux à vos besoins.

  • DDP est le plus simple et colle au principe de base du météore mais les données sont plus volumineux, non compressibles pendant le transfert, non cachables. Mais cette option peut être bonne si vous n'avez besoin que de petits fichiers.
  • XHR couplé avec le système de fichiers est le "traditionnel". API Stable, fast, 'streamable', compressible, cachable( ETag etc), mais doit être dans un dossier séparé
  • XHR couplé avec GridFS, vous obtenez l'avantage de rep set, évolutif, Pas de toucher dir système de fichiers, de gros fichiers et de nombreux fichiers Si système de fichiers restreint les nombres, également compressible cachable. Cependant, L'API est instable, vous obtenez des erreurs dans plusieurs Écritures, c'est s..l'..o..W..

espérons que bientôt, meteor DDP pourra supporter gzip, caching etc et GridFS pourra être plus vite...

71
répondu Green 2015-01-14 00:50:23

Salut juste pour ajouter à Option1 concernant affichage du fichier. Je l'ai fait sans 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
répondu bobobobooo 2018-06-26 09:23:24