Ответ 1
Вы можете легко загружать файлы с помощью Meteor без использования каких-либо пакетов или сторонних разработчиков
Вариант 1: DDP, сохранение файла в коллекции 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})
}
});
Explantion
Сначала файл извлекается из ввода с помощью API файлов HTML5. Читатель создается с использованием нового FileReader. Файл читается как readAsArrayBuffer. Этот arraybuffer, если вы console.log, возвращает {}, и DDP не может отправить это по проводу, поэтому он должен быть преобразован в Uint8Array.
Когда вы помещаете это в Meteor.call, Meteor автоматически запускает EJSON.stringify(Uint8Array) и отправляет его с помощью DDP. Вы можете проверить данные в chrome console websocket traffic, вы увидите строку, похожую на base64
На стороне сервера Meteor вызывает EJSON.parse() и преобразует его обратно в буфер
Pros
- Простой, без взлома, без дополнительных пакетов
- Придерживайтесь данных о принципе проводки.
против
- Больше полосы пропускания: итоговая base64-строка на ~ 33% больше, чем исходный файл
- Ограничение размера файла: невозможно отправить большие файлы (ограничение ~ 16 МБ?)
- Нет кеширования
- Отсутствует 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" на сервер.
На сервере данные передаются в базовую файловую систему. Вы можете дополнительно определить имя файла, выполнить санитарию или проверить, существует ли оно уже и т.д. Перед сохранением.
Pros
- Воспользовавшись XHR 2, вы можете отправить arraybuffer, новый FileReader() не требуется по сравнению с вариантом 1
- Arraybuffer менее громоздкий по сравнению со строкой base64
- Ограничение по размеру, я отправил файл ~ 200 МБ в localhost без проблем
- Файловая система быстрее, чем mongodb (подробнее об этом ниже в сравнительном анализе ниже)
- Cachable и gzip
против
- XHR 2 недоступен в старых браузерах, например. ниже IE10, но, конечно, вы можете реализовать традиционную запись <form> Я использовал xhr = new XMLHttpRequest(), а не HTTP.call('POST'), потому что текущий HTTP.call в Meteor еще не может отправить arraybuffer (укажите мне, если я ошибаюсь).
- /path/to/dir/должен быть вне метеор, иначе запись файла в /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);
});
Объяснение
Клиент script такой же, как в опции 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'), этот монго является последним v2.0.13 на момент написания этой статьи. Новый API не требует, чтобы вы открывали файл, вы можете напрямую вызвать поток (true) и запустить трубопровод
Pros
- То же, что и в варианте 2, отправленном с использованием arraybuffer, меньше накладных расходов по сравнению с base64 string в опции 1
- Не нужно беспокоиться о том, что имя файла называется sanitisation
- Разделение из файловой системы, нет необходимости писать в temp dir, db может быть скопирован, rep, shard и т.д.
- Не нужно внедрять какой-либо другой пакет.
- Cachable и может быть gzipped
- Хранить гораздо большие размеры по сравнению с обычной коллекцией mongo.
- Использование трубки для уменьшения перегрузки памяти
против
- Нестабильный Mongo GridFS. Я включил версию A (mongo 1.x) и B (mongo 2.x). В версии A при больших файлах > 10 МБ, я получил много ошибок, включая поврежденный файл, незавершенный канал. Эта проблема решена в версии B с использованием mongo 2.x, надеюсь, что метеор обновится до mongodb 2.x скоро
- Путаница API. В версии A вам необходимо открыть файл перед потоком, но в версии B вы можете передавать поток без вызова open. API-документ также не очень ясен, и поток не является 100% -ным синтаксисом, заменяемым с Npm.require('fs'). В fs вы вызываете file.on('finish'), но в GridFS вы вызываете file.on('end') при написании заканчивается/заканчивается.
- GridFS не обеспечивает атомарность записи, поэтому, если в один и тот же файл имеется несколько одновременных записей, конечный результат может быть очень различным.
- Скорость. Mongo GridFS намного медленнее, чем файловая система.
Benchmark Вы можете видеть в опции 2 и 3, я включил var start = Date.now(), а при записи конца я console.log выведет время в ms, ниже приведен результат. Dual Core, 4 ГБ, жесткий диск, 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 МБ требуется ~ 80 секунд, используя GridFS, но только ~ 1 сек в FS. Я не пробовал SSD, результат может быть другим. Однако в реальной жизни пропускная способность может определять, насколько быстро поток данных передается от клиента к серверу, а скорость передачи данных 200 МБ/с не типична. С другой стороны, скорость передачи ~ 2 МБ/сек (GridFS) является более нормой.
Заключение
Отнюдь не это всеобъемлющее, но вы можете решить, какой вариант лучше всего подходит для ваших нужд.
- DDP является самым простым и придерживается основного принципа Meteor, но данные более громоздки, не сжимаются во время передачи, а не кэшируются. Но этот вариант может быть хорошим, если вам нужны только небольшие файлы.
- XHR в сочетании с файловой системой является "традиционным" способом. Стабильный API, быстрый, "потоковый", сжимаемый, кэшируемый (ETag и т.д.), Но должен находиться в отдельной папке
- XHR в сочетании с GridFS, вы получаете преимущество набора rep, масштабируемого, не касающегося файловой системы dir, больших файлов и многих файлов, если файловая система ограничивает числа, также сжимаемые с возможностью сжатия. Однако API нестабилен, вы получаете ошибки в нескольких записях, это s..l..o..w..
Скорее всего, метеорит DDP может поддерживать gzip, кеширование и т.д., а GridFS может быть быстрее...