Meteor: wgrywanie pliku z Klienta do kolekcji Mongo vs system plików vs GridFS

Meteor jest świetny, ale brakuje w nim natywnego wsparcia dla tradycyjnego przesyłania plików. Istnieje kilka opcji obsługi przesyłania plików:

od klienta Dane mogą być przesyłane za pomocą:

    Meteor.call ('saveFile', data) lub collection.insert ({plik:DANE})
  • formularz'POST' lub HTTP.call ('POST')

na serwerze Plik można zapisać do:

  • zbiór plików mongodb po zbiorze.insert ({plik:DANE})
  • system plików w /path/to / dir
  • mongodb GridFS

Jakie są plusy i minusy tych metod i jak najlepiej je wdrożyć? Zdaję sobie sprawę, że istnieją również inne opcje, takie jak zapisywanie na stronie trzeciej i uzyskanie adresu url.

Author: Green, 2015-01-14

2 answers

Możesz po prostu przesłać plik za pomocą Meteor, nie używając więcej pakietów ani strony trzeciej

Opcja 1: DDP, zapisanie pliku do kolekcji 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})         
    }   
});

Wyjaśnienie

Najpierw plik jest pobierany z wejścia za pomocą interfejsu API plików HTML5. Czytnik jest tworzony przy użyciu nowego programu FileReader. Plik jest odczytywany jako readAsArrayBuffer. Ten arraybuffer, jeśli konsoli.log, returns {} i DDP nie może tego wysłać przez przewód, więc musi być przekonwertowane na Uint8Array.

Kiedy umieścisz to w Meteorze.call, Meteor automatycznie uruchamia EJSONA.stringify (Uint8Array) i wysyła go z DDP. Możesz sprawdzić dane w ruchu websocket konsoli chrome, zobaczysz ciąg przypominający base64

Po stronie serwera, Meteor call EJSON.parse () i konwertuje ją z powrotem do bufora

Plusy

  1. proste, bez hacky sposób, bez dodatkowych pakietów
  2. Trzymaj Się danych na drucie zasada

Cons

  1. większa przepustowość: wynikowy łańcuch base64 jest ~ 33% większy niż oryginalny plik
  2. limit rozmiaru pliku: nie można wysyłać dużych plików (limit ~ 16 MB?)
  3. brak buforowania
  4. brak jeszcze gzip ani kompresji
  5. zajmują dużo pamięci, jeśli publikujesz pliki

Opcja 2: XHR, post z Klienta do systemu plików

/*** 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
});

Wyjaśnienie

Plik w kliencie jest przechwytywany, an Zostanie wytworzony obiekt XHR i plik zostanie wysłany poprzez 'POST' do serwera.

Na serwerze dane są przesyłane do bazowego systemu plików. Możesz dodatkowo określić nazwę pliku, przeprowadzić sanityzację lub sprawdzić, czy już istnieje itd. przed zapisaniem.

Plusy

  1. korzystając z XHR 2, aby móc wysyłać arraybuffer, nie jest potrzebna żadna nowa funkcja FileReader () w porównaniu z opcją 1
  2. Arraybuffer jest mniej nieporęczny w porównaniu z łańcuchem base64
  3. nie limit rozmiaru, wysłałem plik ~ 200 MB w localhost bez problemu
  4. System plików jest szybszy niż mongodb (więcej w benchmarkingu poniżej)
  5. C i gzip

Cons

  1. XHR 2 nie jest dostępny w starszych przeglądarkach, np. poniżej IE10, ale oczywiście można zaimplementować tradycyjny post
    użyłem tylko XHR = new XMLHttpRequest (), zamiast HTTP.call ('POST'), ponieważ bieżący HTTP.call in Meteor nie jest jeszcze w stanie wysłać arraybuffer (wskaż mi, jeśli się mylę).
  2. W przeciwieństwie do Meteora, nie jest on w stanie odczytać pliku z katalogu, ponieważ nie jest on w stanie odczytać pliku z katalogu.]}

Opcja 3: XHR, Zapisz do 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);
});     

Wyjaśnienie

Skrypt klienta jest taki sam jak w opcji 2.

Według Meteor 1.0.x mongo_driver.js ostatnia linia , obiekt globalny o nazwie MongoInternals jest odsłonięty, możesz wywołać defaultRemoteCollectionDriver (), aby zwrócić bieżący obiekt bazy danych db, który jest wymagany dla magazynu GridStore. W wersji A GridStore jest również eksponowane przez MongoInternals. Mongo używane przez obecny meteor to v1.4.x

Następnie wewnątrz trasy można utworzyć nowy obiekt write, wywołując var file = new gridstore(...) (API ). Następnie otwierasz plik i tworzysz strumień.

Dodałem również wersję B. w tej wersji GridStore jest wywoływany przy użyciu nowego napędu mongodb poprzez Npm.require ('mongodb'), ten mongo jest najnowszym v2. 0. 13 w chwili pisania tego tekstu. Nowy API nie wymaga otwierania pliku, możesz wywołać stream (true) bezpośrednio i rozpocząć Orurowanie

Plusy

  1. to samo co w opcji 2, wysyłane za pomocą arraybuffer, mniej narzutu w porównaniu do ciągu Base64 w opcji 1
  2. nie musisz się martwić o sanityzację nazwy pliku
  3. oddzielenie od systemu plików, nie ma potrzeby zapisu do katalogu temp, db może być backupowane, rep, shard itp
  4. nie trzeba wdrażać żadnych innych pakiet
  5. Cachable I can be gziped
  6. przechowywać znacznie większe rozmiary w porównaniu do normalnej kolekcji mongo
  7. użycie rury do zmniejszenia przeciążenia pamięci

Cons

  1. Unstable Mongo GridFS. W zestawie Wersja A (mongo 1.x) I B (mongo 2.x). W wersji A, przy orurowaniu dużych plików > 10 MB, otrzymałem wiele błędów, w tym uszkodzony plik, niedokończony przewód. Problem ten został rozwiązany w wersji B przy użyciu mongo 2.x, mam nadzieję, że meteor będzie uaktualnij do mongodb 2.x soon
  2. API zamieszanie. W wersji A musisz otworzyć plik, zanim będziesz mógł przesyłać strumieniowo, ale w wersji B możesz przesyłać strumieniowo bez wywoływania open. API doc również nie jest bardzo jasne i strumień nie jest w 100% składni wymienny z Npm.require ("fs"). W fs wywołasz plik.on ('finish') ale w GridFS wywołujesz file.on ('end') podczas pisania finishes / ends.
  3. GridFS nie zapewnia atomiczności zapisu, więc jeśli istnieje wiele jednoczesnych zapisów do ten sam plik, wynik końcowy może być bardzo różny
  4. Speed . Mongo GridFS jest znacznie wolniejszy niż system plików.

Benchmark Możesz zobaczyć w opcji 2 i opcji 3, i included var start = Date.teraz () i pisząc koniec, pocieszam się.Wyloguj czas w ms , poniżej jest wynik. Dwurdzeniowy, 4 GB ram, HDD, oparty na ubuntu 14.04.

file size   GridFS  FS
100 KB      50      2
1 MB        400     30
10 MB       3500    100
200 MB      80000   1240

Widać, że FS jest znacznie szybszy niż GridFS. Dla pliku 200 MB zajmuje to ~ 80 sek przy użyciu GridFS ale tylko ~ 1 sek W FS. Nie próbowałem SSD, wynik może być inny. Jednak w prawdziwym życiu, przepustowość może dyktować, jak szybko plik jest przesyłany strumieniowo od klienta do serwera, osiągnięcie prędkości transferu 200 MB / s nie jest typowe. Z drugiej strony szybkość transferu ~2 MB / s (GridFS) jest bardziej normą.

Wniosek

Bynajmniej nie jest to kompleksowe, ale możesz zdecydować, która opcja jest najlepsza dla Twoich potrzeb.

  • DDP jest najprostszy i przykleja się do podstawowa zasada Meteor, ale dane są bardziej nieporęczne, nie Kompresowalne podczas transferu, nie Cache. Ale ta opcja może być dobra, jeśli potrzebujesz tylko małych plików.
  • XHR w połączeniu z systemem plików jest 'tradycyjnym' sposobem. W 2008 roku firma została założona przez firmę CAG-A, która od 2009 roku zajmuje się dystrybucją i dystrybucją sprzętu komputerowego.]}
  • XHR w połączeniu z GridFS , otrzymujesz korzyści z zestawu rep, skalowalnego, bez dotykania katalogu systemu plików, dużych plików i wielu plików jeśli system plików ogranicza liczby, również cachable compressible. Jednak API jest niestabilne, dostajesz błędy w wielu zapisach, to s..l..o..w..

Miejmy nadzieję, że wkrótce meteor DDP będzie mógł obsługiwać gzip, buforowanie itp., a GridFS może być szybszy ...

 71
Author: Green,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 00:50:23

Witam tylko dodam do Opcji1 odnośnie oglądania pliku. Zrobiłem to bez ejsona.

<template name='tryUpload'>
  <p>Choose file to upload</p>
  <input name="upload" class='fileupload' type='file'>
</template>

Template.tryUpload.events({
'change .fileupload':function(event,template){
console.log('change & view');
var f = event.target.files[0];//assuming upload 1 file only
if(!f) return;
var r = new FileReader();
r.onload=function(event){
  var buffer = new Uint8Array(r.result);//convert to binary
  for (var i = 0, strLen = r.length; i < strLen; i++){
    buffer[i] = r.charCodeAt(i);
  }
  var toString = String.fromCharCode.apply(null, buffer );
  console.log(toString);
  //Meteor.call('saveFiles',buffer);
}
r.readAsArrayBuffer(f);};
 0
Author: bobobobooo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 09:23:24