MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Meteor:качване на файл от клиент в колекция Mongo срещу файлова система срещу GridFS

Можете да постигнете качване на файлове с 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() и го преобразува обратно в буфер

Плюсове

  1. Прост, без хакерски начин, без допълнителни пакети
  2. Придържайте се към принципа Data on the Wire

Против

  1. Повече честотна лента:полученият низ base64 е с ~ 33% по-голям от оригиналния файл
  2. Ограничение за размера на файла:не могат да се изпращат големи файлове (ограничение ~ 16 MB?)
  3. Без кеширане
  4. Все още няма gzip или компресия
  5. Заемате много памет, ако публикувате файлове

Опция 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' към сървъра.

На сървъра данните се прехвърлят в основна файлова система. Можете допълнително да определите името на файла, да извършите дезинфекция или да проверите дали вече съществува и т.н., преди да запазите.

Плюсове

  1. Възползвайки се от XHR 2, за да можете да изпращате буфер на масив, не е необходим нов FileReader() в сравнение с опция 1
  2. Arraybuffer е по-малко обемист в сравнение с base64 низ
  3. Без ограничение за размера, изпратих файл ~ 200 MB в localhost без проблем
  4. Файловата система е по-бърза от mongodb (повече от това по-късно в сравнителния анализ по-долу)
  5. Кеширане и gzip

Против

  1. XHR 2 не е наличен в по-стари браузъри, напр. под IE10, но разбира се можете да внедрите традиционна публикация
    . Използвах само xhr =new XMLHttpRequest(), вместо HTTP.call('POST'), тъй като текущият HTTP.call в Meteor все още не може да изпрати буфер на масив (посочете ме, ако греша).
  2. /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) и да започнете да изпращате по канал

Плюсове

  1. Същото като при опция 2, изпратено чрез буфер на масив, по-малко допълнителни разходи в сравнение с низа base64 в опция 1
  2. Няма нужда да се притеснявате за санирането на името на файла
  3. Разделяне от файловата система, няма нужда да се пише във временна директория, db може да се архивира, република, шард и т.н.
  4. Няма нужда от внедряване на друг пакет
  5. Кеширане и може да се архивира
  6. Съхранявайте много по-големи размери в сравнение с нормалната колекция монго
  7. Използване на тръба за намаляване на претоварването на паметта

Против

  1. Нестабилна Mongo GridFS . Включих версия A (mongo 1.x) и B (mongo 2.x). Във версия A, когато изпращам големи файлове> 10 MB, получих много грешки, включително повреден файл, недовършен канал. Този проблем е решен във версия B с помощта на mongo 2.x, да се надяваме, че meteor скоро ще надстрои до mongodb 2.x
  2. Объркване на API . Във версия A трябва да отворите файла, преди да можете да предавате поточно, но във версия B можете да предавате поточно, без да извиквате open. Документът за API също не е много ясен и потокът не е 100% синтаксис за обмен с Npm.require('fs'). Във fs извиквате file.on('finish'), но в GridFS извиквате file.on('end'), когато записът завършва/приключва.
  3. GridFS не осигурява атомарност на запис, така че ако има няколко едновременни записи в един и същ файл, крайният резултат може да е много различен
  4. Скорост . 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 може да бъде по-бърз ...



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Получаване на времева марка от mongodb id

  2. Предварителна защита с регистриране на одит за MongoDB

  3. Flask - Лоша заявка Браузърът (или прокси) изпрати заявка, която този сървър не може да разбере

  4. Не мога да се свържа с MongoDB Atlas (queryTxt ETIMEOUT)

  5. Как да сумирате стойността на ключ във всички документи в колекция MongoDB