Sie können Dateien mit Meteor hochladen, ohne weitere Pakete oder einen Drittanbieter zu verwenden
Option 1:DDP, Datei in einer Mongo-Sammlung speichern
/*** 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})
}
});
Erklärung
Zunächst wird die Datei mithilfe der HTML5-Datei-API aus der Eingabe abgerufen. Ein Reader wird mit dem neuen FileReader erstellt. Die Datei wird als readAsArrayBuffer gelesen. Dieser Arraybuffer gibt, wenn Sie console.log verwenden, {} zurück und DDP kann dies nicht über die Leitung senden, also muss es in Uint8Array konvertiert werden.
Wenn Sie dies in Meteor.call einfügen, führt Meteor automatisch EJSON.stringify(Uint8Array) aus und sendet es mit DDP. Sie können die Daten im Websocket-Traffic der Chrome-Konsole überprüfen. Sie sehen eine Zeichenfolge, die base64 ähnelt
Auf der Serverseite ruft Meteor EJSON.parse() auf und konvertiert es zurück in den Puffer
Vorteile
- Einfach, keine Tricks, keine zusätzlichen Pakete
- Halten Sie sich an das Data-on-the-Wire-Prinzip
Nachteile
- Mehr Bandbreite:Die resultierende base64-Zeichenfolge ist ~ 33 % größer als die Originaldatei
- Dateigrößenbeschränkung:Große Dateien können nicht gesendet werden (Limit ~ 16 MB?)
- Kein Caching
- Noch kein gzip oder Komprimierung
- Nehmen viel Speicherplatz in Anspruch, wenn Sie Dateien veröffentlichen
Option 2:XHR, Post vom Client zum Dateisystem
/*** 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
});
Erklärung
Die Datei im Client wird gegrabbt, ein XHR-Objekt erstellt und die Datei per 'POST' an den Server gesendet.
Auf dem Server werden die Daten in ein zugrunde liegendes Dateisystem geleitet. Sie können zusätzlich den Dateinamen bestimmen, eine Bereinigung durchführen oder vor dem Speichern prüfen, ob er bereits existiert etc.
Vorteile
- Wenn Sie XHR 2 nutzen, um Arraybuffer zu senden, ist im Vergleich zu Option 1 kein neuer FileReader() erforderlich
- Arraybuffer ist weniger sperrig im Vergleich zu base64-Strings
- Keine Größenbeschränkung, ich habe eine Datei von ~ 200 MB in localhost ohne Probleme gesendet
- Dateisystem ist schneller als mongodb (mehr dazu später im Benchmarking unten)
- Zwischenspeicherbar und gzip
Nachteile
- XHR 2 ist in älteren Browsern nicht verfügbar, z. unter IE10, aber natürlich können Sie ein traditionelles post
- /path/to/dir/ muss außerhalb von meteor liegen, sonst löst das Schreiben einer Datei in /public einen Reload aus
Option 3:XHR, in GridFS speichern
/*** 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);
});
Erklärung
Das Clientskript ist dasselbe wie in Option 2.
Laut der letzten Zeile von Meteor 1.0.x mongo_driver.j wird ein globales Objekt namens MongoInternals bereitgestellt. Sie können defaultRemoteCollectionDriver() aufrufen, um das aktuelle Datenbank-DB-Objekt zurückzugeben, das für den GridStore erforderlich ist. In Version A wird der GridStore auch von MongoInternals verfügbar gemacht. Der vom aktuellen Meteor verwendete Mongo ist v1.4.x
Dann können Sie innerhalb einer Route ein neues Schreibobjekt erstellen, indem Sie var file =new GridStore(...) (API) aufrufen. Anschließend öffnen Sie die Datei und erstellen einen Stream.
Ich habe auch eine Version B hinzugefügt. In dieser Version wird der GridStore mit einem neuen Mongodb-Laufwerk über Npm.require('mongodb') aufgerufen, dieser Mongo ist zum Zeitpunkt des Schreibens die neueste Version 2.0.13. Die neue API erfordert nicht, dass Sie die Datei öffnen, Sie können stream(true) direkt aufrufen und mit dem Piping beginnen
Vorteile
- Wie in Option 2, gesendet mit Arraybuffer, weniger Overhead im Vergleich zum base64-String in Option 1
- Sie müssen sich keine Gedanken über die Bereinigung von Dateinamen machen
- Trennung vom Dateisystem, keine Notwendigkeit, in das Temp-Verzeichnis zu schreiben, die Datenbank kann gesichert werden, Rep, Shard usw.
- Keine Notwendigkeit, ein anderes Paket zu implementieren
- Zwischenspeicherbar und gzippbar
- Bewahren Sie im Vergleich zur normalen Mongo-Kollektion viel größere Größen auf
- Verwendung von Pipe zur Reduzierung der Speicherüberlastung
Nachteile
- Instabiles Mongo GridFS . Ich habe Version A (Mongo 1.x) und B (Mongo 2.x) hinzugefügt. In Version A bekam ich beim Pipen von großen Dateien> 10 MB viele Fehler, einschließlich beschädigter Dateien, unfertiger Pipes. Dieses Problem wurde in Version B mit Mongo 2.x gelöst, hoffentlich wird Meteor bald auf Mongodb 2.x aktualisieren
- API-Verwirrung . In Version A müssen Sie die Datei öffnen, bevor Sie streamen können, aber in Version B können Sie streamen, ohne open aufzurufen. Das API-Dokument ist auch nicht sehr klar und der Stream ist nicht zu 100% syntaktisch mit Npm.require('fs') austauschbar. In fs rufen Sie file.on('finish') auf, aber in GridFS rufen Sie file.on('end') auf, wenn das Schreiben beendet/endet.
- GridFS bietet keine Schreibatomarität. Wenn also mehrere gleichzeitige Schreibvorgänge in dieselbe Datei erfolgen, kann das Endergebnis sehr unterschiedlich sein
- Geschwindigkeit . Mongo GridFS ist viel langsamer als das Dateisystem.
Benchmark Sie können in Option 2 und Option 3 sehen, dass ich var start =Date.now() eingefügt habe und beim Schreiben von end, ich console.logout die Zeit in ms , unten ist das Ergebnis. Dual Core, 4 GB RAM, HDD, basierend auf Ubuntu 14.04.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Sie können sehen, dass FS viel schneller ist als GridFS. Für eine Datei von 200 MB dauert es ~ 80 Sekunden mit GridFS, aber nur ~ 1 Sekunde in FS. Ich habe SSD nicht ausprobiert, das Ergebnis kann anders sein. Im wirklichen Leben kann die Bandbreite jedoch bestimmen, wie schnell die Datei vom Client zum Server gestreamt wird, das Erreichen einer Übertragungsgeschwindigkeit von 200 MB/s ist nicht typisch. Andererseits ist eine Übertragungsgeschwindigkeit von ~2 MB/s (GridFS) eher die Norm.
Fazit
Dies ist keineswegs umfassend, aber Sie können entscheiden, welche Option für Ihre Anforderungen am besten geeignet ist.
- DDP ist das einfachste und hält sich an das grundlegende Meteor-Prinzip, aber die Daten sind umfangreicher, während der Übertragung nicht komprimierbar und nicht zwischenspeicherbar. Aber diese Option kann gut sein, wenn Sie nur kleine Dateien benötigen.
- XHR mit Dateisystem gekoppelt ist der "traditionelle" Weg. Stabile API, schnell, „streambar“, komprimierbar, zwischenspeicherbar (ETag usw.), muss sich aber in einem separaten Ordner befinden
- XHR gekoppelt mit GridFS , Sie erhalten den Vorteil eines Rep-Sets, skalierbar, kein berührendes Dateisystemverzeichnis, große Dateien und viele Dateien, wenn das Dateisystem die Anzahl einschränkt, auch komprimierbar im Cache. Die API ist jedoch instabil, Sie erhalten Fehler bei mehreren Schreibvorgängen, es ist s..l..o..w..
Hoffentlich kann Meteor DDP bald gzip, Caching usw. unterstützen und GridFS kann schneller sein ...