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.
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
- Simple, pas de hacky façon, pas de paquets supplémentaires
- S'en tenir aux données sur le Fil de principe
Cons
- plus de bande passante: la chaîne de caractères base64 résultante est ~ 33% plus grande que le fichier original
- Taille du fichier limite: ne peut pas envoyer de gros fichiers (limite ~ 16 MB?)
- Pas de mise en cache
- Pas de la compression gzip ou encore
- 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
- profitez de XHR 2 pour envoyer arraybuffer, pas de new FileReader() est nécessaire par rapport à l'option 1
- Arraybuffer est moins encombrant par rapport à base64 de la chaîne
- Pas de limite de taille, j'ai envoyé un fichier ~ 200 MO en localhost avec pas de problème
- système de Fichiers est plus rapide que mongodb (plus de cela plus tard dans l'analyse comparative ci-dessous)
- Cachable et gzip
Cons
- 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
- /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
- comme dans l'option 2, envoyé en utilisant arraybuffer, moins de overhead comparé à la chaîne base64 dans l'option 1
- Pas besoin de s'inquiéter à propos de fichier nom de suppression
- Séparation de système de fichiers, pas besoin d'écrire à la température dir, la db peuvent être sauvegardés, rep, tesson etc
- Pas besoin de mettre en œuvre n'importe quel autre paquet
- Cachable " et peut être au format gzip
- stocker des tailles beaucoup plus grandes par rapport à la collection normale de mongo
- utilisation d'un tuyau pour réduire la surcharge de la mémoire
Cons
- 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
- 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.
- 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
- 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...
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);};