Можете да постигнете качване на файлове с Meteor, без да използвате повече пакети или трета страна
Опция 1:DDP, запазване на файл в колекция монго
/*** 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})
}
});
Обяснение
Първо, файлът се грабва от входа с помощта на HTML5 File API. Създава се четец с помощта на нов FileReader. Файлът се чете като readAsArrayBuffer. Този масивен буфер, ако console.log, връща {} и DDP не може да го изпрати по кабела, така че трябва да се преобразува в Uint8Array.
Когато поставите това в Meteor.call, Meteor автоматично стартира EJSON.stringify(Uint8Array) и го изпраща с DDP. Можете да проверите данните в трафика на chrome console websocket, ще видите низ, наподобяващ base64
От страна на сървъра Meteor извиква EJSON.parse() и го преобразува обратно в буфер
Плюсове
- Прост, без хакерски начин, без допълнителни пакети
- Придържайте се към принципа Data on the Wire
Против
- Повече честотна лента:полученият низ base64 е с ~ 33% по-голям от оригиналния файл
- Ограничение за размера на файла:не могат да се изпращат големи файлове (ограничение ~ 16 MB?)
- Без кеширане
- Все още няма gzip или компресия
- Заемате много памет, ако публикувате файлове
Опция 2:XHR, публикуване от клиент към файлова система
/*** 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
});
Обяснение
Файлът в клиента се грабва, създава се XHR обект и файлът се изпраща чрез 'POST' към сървъра.
На сървъра данните се прехвърлят в основна файлова система. Можете допълнително да определите името на файла, да извършите дезинфекция или да проверите дали вече съществува и т.н., преди да запазите.
Плюсове
- Възползвайки се от XHR 2, за да можете да изпращате буфер на масив, не е необходим нов FileReader() в сравнение с опция 1
- Arraybuffer е по-малко обемист в сравнение с base64 низ
- Без ограничение за размера, изпратих файл ~ 200 MB в localhost без проблем
- Файловата система е по-бърза от mongodb (повече от това по-късно в сравнителния анализ по-долу)
- Кеширане и gzip
Против
- XHR 2 не е наличен в по-стари браузъри, напр. под IE10, но разбира се можете да внедрите традиционна публикация
- /path/to/dir/ трябва да е извън meteor, в противен случай записването на файл в /public задейства презареждане
Опция 3:XHR, запишете в 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);
});
Обяснение
Клиентският скрипт е същият като в опция 2.
Според последния ред на Meteor 1.0.x mongo_driver.js е изложен глобален обект, наречен MongoInternals, можете да извикате defaultRemoteCollectionDriver(), за да върнете текущия db обект на базата данни, който е необходим за GridStore. Във версия A GridStore също е изложен от MongoInternals. Монгото, използвано от текущия метеор, е v1.4.x
След това вътре в маршрут можете да създадете нов обект за запис, като извикате var file =new GridStore(...) (API). След това отваряте файла и създавате поток.
Включих и версия B. В тази версия GridStore се извиква с помощта на ново устройство mongodb чрез Npm.require('mongodb'), това mongo е най-новата v2.0.13 към момента на писане. Новият API не изисква от вас да отваряте файла, можете да извикате директно stream(true) и да започнете да изпращате по канал
Плюсове
- Същото като при опция 2, изпратено чрез буфер на масив, по-малко допълнителни разходи в сравнение с низа base64 в опция 1
- Няма нужда да се притеснявате за санирането на името на файла
- Разделяне от файловата система, няма нужда да се пише във временна директория, db може да се архивира, република, шард и т.н.
- Няма нужда от внедряване на друг пакет
- Кеширане и може да се архивира
- Съхранявайте много по-големи размери в сравнение с нормалната колекция монго
- Използване на тръба за намаляване на претоварването на паметта
Против
- Нестабилна Mongo GridFS . Включих версия A (mongo 1.x) и B (mongo 2.x). Във версия A, когато изпращам големи файлове> 10 MB, получих много грешки, включително повреден файл, недовършен канал. Този проблем е решен във версия B с помощта на mongo 2.x, да се надяваме, че meteor скоро ще надстрои до mongodb 2.x
- Объркване на API . Във версия A трябва да отворите файла, преди да можете да предавате поточно, но във версия B можете да предавате поточно, без да извиквате open. Документът за API също не е много ясен и потокът не е 100% синтаксис за обмен с Npm.require('fs'). Във fs извиквате file.on('finish'), но в GridFS извиквате file.on('end'), когато записът завършва/приключва.
- GridFS не осигурява атомарност на запис, така че ако има няколко едновременни записи в един и същ файл, крайният резултат може да е много различен
- Скорост . Mongo GridFS е много по-бавен от файловата система.
Сравнение Можете да видите във вариант 2 и опция 3, включих var start =Date.now() и когато пиша край, конзолирам.излизам от времето в ms , по-долу е резултатът. Двуядрен, 4 GB RAM, твърд диск, базиран на ubuntu 14.04.
file size GridFS FS
100 KB 50 2
1 MB 400 30
10 MB 3500 100
200 MB 80000 1240
Можете да видите, че FS е много по-бърз от GridFS. За файл от 200 MB са необходими ~80 секунди при използване на GridFS, но само ~ 1 секунда в FS. Не съм пробвал SSD, резултатът може да е различен. Въпреки това, в реалния живот, честотната лента може да диктува колко бързо файлът се предава от клиент към сървър, постигането на скорост на трансфер от 200 MB/sec не е типично. От друга страна, скорост на трансфер ~2 MB/sec (GridFS) е по-скоро норма.
Заключение
Това в никакъв случай не е изчерпателно, но можете да решите коя опция е най-подходяща за вашите нужди.
- DDP е най-простият и се придържа към основния принцип на Meteor, но данните са по-обемисти, не се компресират по време на трансфер, не могат да се кешират. Но тази опция може да е добра, ако имате нужда само от малки файлове.
- XHR заедно с файлова система е "традиционният" начин. Стабилен API, бърз, "поточно", компресируем, кешируем (ETag и т.н.), но трябва да е в отделна папка
- XHR заедно с GridFS , получавате предимството на набор от повторения, мащабируем, без докосване директория на файловата система, големи файлове и много файлове, ако файловата система ограничава числата, също и кешираща се компресия. API обаче е нестабилен, получавате грешки при множество записвания, това е s..l..o..w..
Надяваме се скоро, meteor DDP може да поддържа gzip, кеширане и т.н. и GridFS може да бъде по-бърз ...