U kunt bestanden uploaden met Meteor zonder meer pakketten of een derde partij te gebruiken
Optie 1:DDP, bestand opslaan in een mongo-verzameling
/*** 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})
}
});
Uitleg
Eerst wordt het bestand uit de invoer gehaald met behulp van HTML5 File API. Er wordt een reader gemaakt met de nieuwe FileReader. Het bestand wordt gelezen als readAsArrayBuffer. Deze arraybuffer, als je console.log, retourneert {} en DDP kan dit niet over de draad verzenden, dus het moet worden geconverteerd naar Uint8Array.
Wanneer u dit in Meteor.call plaatst, voert Meteor automatisch EJSON.stringify(Uint8Array) uit en verzendt het met DDP. U kunt de gegevens controleren in het websocket-verkeer van de Chrome-console, u ziet een tekenreeks die lijkt op base64
Aan de serverkant roept Meteor EJSON.parse() aan en converteert het terug naar buffer
Pluspunten
- Eenvoudig, geen hacky manier, geen extra pakketten
- Houd je aan het Data on the Wire-principe
Nadelen
- Meer bandbreedte:de resulterende base64-string is ~ 33% groter dan het originele bestand
- Bestandsgroottelimiet:kan geen grote bestanden verzenden (limiet ~ 16 MB?)
- Geen cache
- Nog geen gzip of compressie
- Veel geheugen in beslag nemen als je bestanden publiceert
Optie 2:XHR, post van client naar bestandssysteem
/*** 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
});
Uitleg
Het bestand in de client wordt gepakt, er wordt een XHR-object gemaakt en het bestand wordt via 'POST' naar de server gestuurd.
Op de server worden de gegevens doorgesluisd naar een onderliggend bestandssysteem. U kunt bovendien de bestandsnaam bepalen, sanering uitvoeren of controleren of deze al bestaat enz. voordat u opslaat.
Pluspunten
- Profiterend van XHR 2 zodat u arraybuffer kunt verzenden, is er geen nieuwe FileReader() nodig in vergelijking met optie 1
- Arraybuffer is minder omvangrijk in vergelijking met base64-tekenreeks
- Geen maximale grootte, ik heb zonder problemen een bestand van ~ 200 MB naar localhost verzonden
- Bestandssysteem is sneller dan mongodb (hierover later meer in benchmarking hieronder)
- Cachable en gzip
Nadelen
- XHR 2 is niet beschikbaar in oudere browsers, b.v. onder IE10, maar je kunt natuurlijk een traditionele post implementeren
- /pad/naar/dir/ moet buiten meteor zijn, anders wordt het opnieuw geladen door een bestand in /public te schrijven
Optie 3:XHR, opslaan in 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);
});
Uitleg
Het clientscript is hetzelfde als in optie 2.
Volgens de laatste regel van Meteor 1.0.x mongo_driver.j wordt een globaal object met de naam MongoInternals weergegeven, u kunt defaultRemoteCollectionDriver() aanroepen om het huidige database-db-object te retourneren dat vereist is voor de GridStore. In versie A wordt de GridStore ook zichtbaar gemaakt door de MongoInternals. De mongo die wordt gebruikt door de huidige meteoor is v1.4.x
Vervolgens kunt u binnen een route een nieuw schrijfobject maken door var file =new GridStore(...) (API) aan te roepen. Vervolgens opent u het bestand en maakt u een stream.
Ik heb ook een versie B toegevoegd. In deze versie wordt de GridStore aangeroepen met een nieuwe mongodb-drive via Npm.require('mongodb'), deze mongo is de nieuwste v2.0.13 op het moment van schrijven. Voor de nieuwe API hoeft u het bestand niet te openen, u kunt stream(true) rechtstreeks aanroepen en beginnen met piping
Pluspunten
- Hetzelfde als in optie 2, verzonden met arraybuffer, minder overhead vergeleken met base64 string in optie 1
- U hoeft zich geen zorgen te maken over het opschonen van bestandsnamen
- Scheiding van het bestandssysteem, het is niet nodig om naar de tijdelijke map te schrijven, de db kan worden geback-upt, rep, shard enz.
- Het is niet nodig om een ander pakket te implementeren
- Cachable en kan worden gezipt
- Bewaar veel grotere maten in vergelijking met de normale mongo-collectie
- Pijp gebruiken om geheugenoverbelasting te verminderen
Nadelen
- Instabiele Mongo GridFS . Ik heb versie A (mongo 1.x) en B (mongo 2.x) toegevoegd. In versie A, bij het pipen van grote bestanden> 10 MB, kreeg ik veel fouten, waaronder een beschadigd bestand, onvoltooide pijp. Dit probleem is opgelost in versie B met behulp van mongo 2.x, hopelijk zal meteor binnenkort upgraden naar mongodb 2.x
- API-verwarring . In versie A moet je het bestand openen voordat je kunt streamen, maar in versie B kun je streamen zonder open te bellen. Het API-document is ook niet erg duidelijk en de stream is niet 100% syntaxis uitwisselbaar met Npm.require('fs'). In fs roept u file.on('finish') aan, maar in GridFS roept u file.on('end') aan wanneer het schrijven eindigt/eindigt.
- GridFS biedt geen schrijf-atomiciteit, dus als er meerdere gelijktijdige schrijfacties naar hetzelfde bestand zijn, kan het uiteindelijke resultaat heel anders zijn
- Snelheid . Mongo GridFS is veel langzamer dan het bestandssysteem.
Benchmark Je kunt in optie 2 en optie 3 zien dat ik var start =Date.now() heb toegevoegd en bij het schrijven van end, console.log de tijd uit in ms , hieronder het resultaat. Dual Core, 4 GB ram, HDD, op ubuntu 14.04 gebaseerd.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Je kunt zien dat FS veel sneller is dan GridFS. Voor een bestand van 200 MB duurt het ~80 sec met GridFS, maar slechts ~ 1 sec in FS. SSD heb ik niet geprobeerd, het resultaat kan anders zijn. In het echte leven kan de bandbreedte echter bepalen hoe snel het bestand van client naar server wordt gestreamd, het bereiken van een overdrachtssnelheid van 200 MB/sec is niet gebruikelijk. Aan de andere kant is een overdrachtssnelheid van ~2 MB/sec (GridFS) meer de norm.
Conclusie
Dit is zeker niet allesomvattend, maar u kunt zelf beslissen welke optie het beste bij u past.
- DDP is de eenvoudigste en houdt vast aan het kernprincipe van Meteor, maar de gegevens zijn omvangrijker, niet samendrukbaar tijdens de overdracht, niet in cache op te nemen. Maar deze optie kan goed zijn als je alleen kleine bestanden nodig hebt.
- XHR gekoppeld aan bestandssysteem is de 'traditionele' manier. Stabiele API, snel, 'streamable', compressible, cachable (ETag etc), maar moet in een aparte map staan
- XHR gekoppeld aan GridFS , krijg je het voordeel van rep set, schaalbaar, geen aanraken bestandssysteem dir, grote bestanden en veel bestanden als het bestandssysteem de aantallen beperkt, ook in cache comprimeerbaar. De API is echter onstabiel, je krijgt fouten bij meerdere schrijfacties, het is s..l..o..w..
Hopelijk kan meteor DDP binnenkort gzip, caching enz. ondersteunen en kan GridFS sneller zijn ...