Du kan opnå filupload med Meteor uden at bruge flere pakker eller en tredjepart
Mulighed 1:DDP, gemmer fil til en mongo-samling
/*** 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})
}
});
Forklaring
Først hentes filen fra input ved hjælp af HTML5 File API. En læser oprettes ved hjælp af den nye FileReader. Filen læses som readAsArrayBuffer. Denne arraybuffer, hvis du console.log, returnerer {} og DDP kan ikke sende denne over ledningen, så den skal konverteres til Uint8Array.
Når du sætter dette i Meteor.call, kører Meteor automatisk EJSON.stringify(Uint8Array) og sender det med DDP. Du kan tjekke dataene i chrome-konsollens websocket-trafik, du vil se en streng, der ligner base64
På serversiden kalder Meteor EJSON.parse() og konverterer det tilbage til buffer
Fordele
- Simpel, ingen hacky måde, ingen ekstra pakker
- Hold dig til Data on the Wire-princippet
Udemper
- Mere båndbredde:den resulterende base64-streng er ~ 33 % større end den originale fil
- Grænse for filstørrelse:kan ikke sende store filer (grænse ~ 16 MB?)
- Ingen cachelagring
- Ingen gzip eller komprimering endnu
- Optag meget hukommelse, hvis du udgiver filer
Mulighed 2:XHR, post fra klient til filsystem
/*** 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
});
Forklaring
Filen i klienten gribes, et XHR-objekt oprettes og filen sendes via 'POST' til serveren.
På serveren overføres dataene til et underliggende filsystem. Du kan desuden bestemme filnavnet, udføre desinficering eller kontrollere, om det allerede eksisterer osv., før du gemmer.
Fordele
- Ved at drage fordel af XHR 2, så du kan sende arraybuffer, er ingen ny FileReader() nødvendig sammenlignet med mulighed 1
- Arraybuffer er mindre omfangsrig sammenlignet med base64-streng
- Ingen størrelsesgrænse, jeg sendte en fil ~ 200 MB i localhost uden problemer
- Filsystemet er hurtigere end mongodb (mere af dette senere i benchmarking nedenfor)
- Cachable og gzip
Udemper
- XHR 2 er ikke tilgængelig i ældre browsere, f.eks. under IE10, men du kan selvfølgelig implementere en traditionel post
- /path/to/dir/ skal være uden for meteor, ellers udløser skrivning af en fil i /public en genindlæsning
Mulighed 3:XHR, gem til 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);
});
Forklaring
Klientscriptet er det samme som i mulighed 2.
Ifølge Meteor 1.0.x mongo_driver.js sidste linje, er et globalt objekt kaldet MongoInternals afsløret, du kan kalde defaultRemoteCollectionDriver() for at returnere det aktuelle database-db-objekt, som kræves til GridStore. I version A er GridStore også eksponeret af MongoInternals. Mongoen brugt af nuværende meteor er v1.4.x
Så inde i en rute kan du oprette et nyt skriveobjekt ved at kalde var file =new GridStore(...) (API). Du åbner derefter filen og opretter en stream.
Jeg inkluderede også en version B. I denne version kaldes GridStore ved at bruge et nyt mongodb-drev via Npm.require('mongodb'), denne mongo er den seneste v2.0.13, når dette skrives. Den nye API kræver ikke, at du åbner filen, du kan ringe direkte til stream(true) og begynde at pibe
Fordele
- Samme som i mulighed 2, sendt med arraybuffer, mindre overhead sammenlignet med base64-streng i mulighed 1
- Ingen grund til at bekymre sig om rensning af filnavne
- Adskillelse fra filsystem, ingen grund til at skrive til temp dir, db kan sikkerhedskopieres, rep, shard osv.
- Ingen grund til at implementere nogen anden pakke
- Cachbar og kan gzippes
- Opbevar meget større størrelser sammenlignet med normal mongokollektion
- Brug af pipe til at reducere hukommelsesoverbelastning
Udemper
- Ustabil Mongo GridFS . Jeg inkluderede version A (mongo 1.x) og B (mongo 2.x). I version A, når jeg overførte store filer> 10 MB, fik jeg masser af fejl, inklusive beskadiget fil, ufærdig pipe. Dette problem er løst i version B ved hjælp af mongo 2.x, forhåbentlig vil meteor snart opgradere til mongodb 2.x
- API-forvirring . I version A skal du åbne filen, før du kan streame, men i version B kan du streame uden at kalde åben. API-dokumentet er heller ikke særlig tydeligt, og streamen kan ikke 100% syntaks udskiftes med Npm.require('fs'). I fs kalder du file.on('finish'), men i GridFS kalder du file.on('end'), når skrivning afsluttes/slutter.
- GridFS giver ikke skriveatomicitet, så hvis der er flere samtidige skrivninger til den samme fil, kan det endelige resultat være meget anderledes
- Hastighed . Mongo GridFS er meget langsommere end filsystemet.
Benchmark Du kan se i mulighed 2 og mulighed 3, jeg inkluderede var start =Date.now(), og når jeg skriver slut, console.log jeg tiden ud i ms , nedenfor er resultatet. Dual Core, 4 GB ram, HDD, ubuntu 14.04 baseret.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Du kan se, at FS er meget hurtigere end GridFS. For en fil på 200 MB tager det ~80 sek ved at bruge GridFS, men kun ~ 1 sek i FS. Jeg har ikke prøvet SSD, resultatet kan være anderledes. I det virkelige liv kan båndbredden dog diktere, hvor hurtigt filen streames fra klient til server, og det er ikke typisk at opnå en overførselshastighed på 200 MB/sek. På den anden side er en overførselshastighed ~2 MB/sek (GridFS) mere normen.
Konklusion
Dette er på ingen måde omfattende, men du kan bestemme, hvilken mulighed der passer bedst til dit behov.
- DDP er den enkleste og holder sig til kerne Meteor-princippet, men dataene er mere omfangsrige, ikke komprimerbare under overførsel, ikke cachbare. Men denne mulighed kan være god, hvis du kun har brug for små filer.
- XHR koblet med filsystem er den 'traditionelle' måde. Stabil API, hurtig, 'streambar', komprimerbar, cachbar (ETag osv.), men skal være i en separat mappe
- XHR koblet med GridFS , får du fordelen af rep sæt, skalerbar, ingen berøring filsystem dir, store filer og mange filer, hvis filsystemet begrænser tallene, også cachable komprimerbar. API'et er dog ustabilt, du får fejl i flere skrivninger, det er s..l..o..w..
Forhåbentlig snart kan meteor DDP understøtte gzip, caching osv., og GridFS kan være hurtigere ...