Vous pouvez télécharger des fichiers avec Meteor sans utiliser d'autres packages ou un tiers
Option 1 :DDP, enregistrement du fichier dans une collection 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})
}
});
Explication
Tout d'abord, le fichier est extrait de l'entrée à l'aide de l'API de fichier HTML5. Un lecteur est créé à l'aide du nouveau FileReader. Le fichier est lu en tant que readAsArrayBuffer. Ce tampon de tableau, si vous console.log, renvoie {} et DDP ne peut pas l'envoyer sur le réseau, il doit donc être converti en Uint8Array.
Lorsque vous mettez ceci dans Meteor.call, Meteor exécute 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, Meteor appelle EJSON.parse() et le reconvertit en buffer
Avantages
- Simple, sans piratage, sans forfaits supplémentaires
- Reste au principe Data on the Wire
Inconvénients
- Plus de bande passante :la chaîne base64 résultante est environ 33 % plus grande que le fichier d'origine
- Limite de taille de fichier :impossible d'envoyer des fichiers volumineux (limite d'environ 16 Mo ?)
- Pas de mise en cache
- Pas encore de gzip ni de compression
- Prenez beaucoup de mémoire si vous publiez des fichiers
Option 2 :XHR, publication du client vers le 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 dans le client est saisi, un objet XHR est créé et le fichier est envoyé via 'POST' au serveur.
Sur le serveur, les données sont dirigées vers un système de fichiers sous-jacent. Vous pouvez en outre déterminer le nom du fichier, effectuer une désinfection ou vérifier s'il existe déjà, etc. avant de l'enregistrer.
Avantages
- En tirant parti de XHR 2 pour pouvoir envoyer un arraybuffer, aucun nouveau FileReader() n'est nécessaire par rapport à l'option 1
- Arraybuffer est moins volumineux que la chaîne base64
- Pas de limite de taille, j'ai envoyé un fichier ~ 200 Mo en localhost sans problème
- Le système de fichiers est plus rapide que mongodb (plus de détails plus tard dans l'analyse comparative ci-dessous)
- Cachable et gzip
Inconvénients
- XHR 2 n'est pas disponible dans les anciens navigateurs, par ex. sous IE10, mais bien sûr vous pouvez implémenter un post traditionnel
- /path/to/dir/ doit être en dehors de meteor, sinon l'écriture d'un fichier dans /public déclenche un rechargement
Option 3 :XHR, enregistrer dans 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 script client est le même que dans l'option 2.
Selon la dernière ligne de Meteor 1.0.x mongo_driver.js, un objet global appelé MongoInternals est exposé, vous pouvez appeler defaultRemoteCollectionDriver() pour renvoyer l'objet de base de données actuel 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 la v1.4.x
Ensuite, à l'intérieur d'une route, vous pouvez créer un nouvel objet d'écriture en appelant var file =new GridStore(...) (API). Vous ouvrez ensuite le fichier et créez un flux.
J'ai également inclus une version B. Dans cette version, le GridStore est appelé à l'aide d'un nouveau lecteur mongodb via Npm.require('mongodb'), ce mongo est la dernière v2.0.13 à ce jour. La nouvelle API ne vous oblige pas à ouvrir le fichier, vous pouvez appeler stream(true) directement et démarrer le pipe
Avantages
- Identique à l'option 2, envoyé à l'aide de arraybuffer, moins de surcharge par rapport à la chaîne base64 de l'option 1
- Pas besoin de s'inquiéter du nettoyage des noms de fichiers
- Séparation du système de fichiers, pas besoin d'écrire dans le répertoire temporaire, la base de données peut être sauvegardée, rep, shard etc
- Pas besoin d'implémenter un autre package
- Cachable et gzippable
- Stockez des tailles beaucoup plus grandes par rapport à la collection mongo normale
- Utilisation de pipe pour réduire la surcharge de la mémoire
Inconvénients
- Mongo GridFS instable . J'ai inclus la version A (mongo 1.x) et B (mongo 2.x). Dans la version A, lors de la canalisation de fichiers volumineux> 10 Mo, j'ai eu beaucoup d'erreurs, y compris un fichier corrompu, un tuyau inachevé. Ce problème est résolu dans la version B en utilisant mongo 2.x, espérons que meteor passera bientôt à mongodb 2.x
- Confusion API . Dans la version A, vous devez ouvrir le fichier avant de pouvoir diffuser, mais dans la version B, vous pouvez diffuser sans appeler open. La documentation de l'API n'est pas non plus très claire et le flux n'est pas échangeable à 100 % avec la syntaxe Npm.require('fs'). Dans fs, vous appelez file.on('finish') mais dans GridFS vous appelez file.on('end') lors de l'écriture se termine/se termine.
- GridFS ne fournit pas d'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.
Référence Vous pouvez voir dans l'option 2 et l'option 3, j'ai inclus var start =Date.now() et lors de l'écriture de fin, je console.log out le temps en ms , ci-dessous le résultat. Dual Core, 4 Go de RAM, disque dur, basé sur Ubuntu 14.04.
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, cela prend ~80 sec en utilisant GridFS mais seulement ~ 1 sec en FS. Je n'ai pas essayé de SSD, le résultat peut être différent. Cependant, dans la vraie vie, la bande passante peut dicter la vitesse à laquelle le fichier est diffusé du client au serveur, atteindre une vitesse de transfert de 200 Mo/sec n'est pas typique. En revanche, une vitesse de transfert d'environ 2 Mo/sec (GridFS) est plus la norme.
Conclusion
Ce n'est en aucun cas exhaustif, mais vous pouvez décider quelle option correspond le mieux à vos besoins.
- DDP est le plus simple et colle au principe de base de Meteor mais les données sont plus volumineuses, non compressibles lors du transfert, non cachables. Mais cette option peut être utile si vous n'avez besoin que de petits fichiers.
- XHR couplé au système de fichiers est la méthode "traditionnelle". API stable, rapide, "streamable", compressible, pouvant être mise en cache (ETag, etc.), mais doit être dans un dossier séparé
- XHR couplé à GridFS , vous bénéficiez d'un jeu de représentants, évolutif, sans toucher au répertoire du système de fichiers, aux fichiers volumineux et à de nombreux fichiers si le système de fichiers limite les nombres, également compressible en cache. Cependant, l'API est instable, vous obtenez des erreurs dans plusieurs écritures, c'est lent..
Espérons que bientôt, meteor DDP pourra prendre en charge gzip, la mise en cache, etc. et GridFS pourra être plus rapide ...