2013-06-12 9 views
5

Muszę zezwolić użytkownikowi mojej aplikacji na pobranie pliku z Meteorem. Obecnie to, co robię, to kiedy użytkownik żąda pobrania pliku, który wprowadzam do zbioru "fileRequests" w Mongo, dokumentu z lokalizacją pliku i znacznikiem czasu żądania oraz zwraca identyfikator nowo utworzonego żądania. Gdy klient otrzymuje nowy identyfikator, trafia do strony mydomain.com/uploads/id. Następnie używać coś takiego przechwycić żądania przed Meteor robi:Jak używać systemu plików createReadStream z routerem Meteor (NodeJS)

var connect = Npm.require("connect"); 
var Fiber = Npm.require("fibers"); 
var path = Npm.require('path'); 
var fs = Npm.require("fs"); 
var mime = Npm.require("mime"); 

__meteor_bootstrap__.app 
    .use(connect.query()) 
    .use(connect.bodyParser()) //I add this for file-uploading 
    .use(function (req, res, next) { 
     Fiber(function() { 

      if(req.method == "GET") { 
       // get the id here, and stream the file using fs.createReadStream(); 
      } 
      next(); 
     }).run(); 
    }); 

I upewnij się prośba plik powstał mniej niż 5 sekund temu, a ja natychmiast usunąć dokument zapytania po tym jak zapytaliśmy go .

To działa i jest bezpieczne (wystarczy). Nikt nie może złożyć żądania bez zalogowania się, a 5 sekund to bardzo małe okno, aby ktoś mógł przeskoczyć utworzony adres URL żądania, ale po prostu nie czuję się dobrze z moim rozwiązaniem. Czuje się brudno!

Tak więc spróbowałem użyć Meteor-Router, aby osiągnąć to samo. W ten sposób mogę sprawdzić, czy są zalogowani poprawnie, bez 5-sekundowego otwarcia na oszustwo na świecie.

Więc oto kod, który napisałem dla:

Meteor.Router.add('/uploads/:id', function(id) { 

    var path = Npm.require('path'); 
    var fs = Npm.require("fs"); 
    var mime = Npm.require("mime"); 

    var res = this.response; 

    var file = FileSystem.findOne({ _id: id }); 

    if(typeof file !== "undefined") { 
     var filename = path.basename(file.filePath); 
     var filePath = '/var/MeteorDMS/uploads/' + filename; 

     var stat = fs.statSync(filePath); 

     res.setHeader('Content-Disposition', 'attachment; filename=' + filename); 
     res.setHeader('Content-Type', mime.lookup(filePath)); 
     res.setHeader('Content-Length', stat.size); 

     var filestream = fs.createReadStream(filePath); 

     filestream.pipe(res); 

     return; 
    } 
}); 

To wygląda świetnie, idealnie wpasowuje się w pozostałej części kodu i jest łatwy do odczytania, nie hacking zaangażowany, ALE! To nie działa! Przeglądarka obraca się i obraca i nigdy nie wie, co robić. Pojawiają się komunikaty o błędach ZERO. Mogę nadal korzystać z aplikacji na innych kartach. Nie wiem, co robi, nigdy nie przestaje "ładować". Jeśli zrestartuję serwer, otrzymam plik 0 bajtów ze wszystkimi poprawnymi nagłówkami, ale nie otrzymam danych.

Każda pomoc jest bardzo ceniona !!

EDIT:

Po wykopaniu się trochę więcej, zauważyłem, że stara się obrócić obiekt odpowiedzi w wynikach obiekt JSON w okrągłym błędu konstrukcji.

Ciekawą rzeczą jest to, że podczas odsłuchiwania strumienia plików dla zdarzenia "data" i próby wiązania obiektu odpowiedzi, nie otrzymuję tego błędu. Ale jeśli spróbuję zrobić to samo w moim pierwszym rozwiązaniu (posłuchaj "danych" i uszereguj odpowiedź), otrzymuję błąd ponownie.

Tak więc przy użyciu rozwiązania Meteor-Router coś dzieje się z obiektem odpowiedzi. Zauważyłem również, że na "danych" wydarzenie response.finished jest oflagowane jako prawdziwe.

filestream.on('data', function(data) { 
    fs.writeFile('/var/MeteorDMS/afterData', JSON.stringify(res)); 
}); 
+0

Mamy ten sam problem. Dowiedzieliśmy się, że są dwa problemy w jednym. Musisz wykonać 'return false;', wywoła to oprogramowanie pośredniczące 'next()' here: https://github.com/tmeasday/meteor-router/blob/master/lib/router_server.js#L86, ale 'pipe() 'nie działa również. Nadal badamy. – nalply

Odpowiedz

1

Router Meteor instaluje oprogramowanie pośrednie, aby przeprowadzić routing. Wszystkie programy pośrednie Connect MUSZĄ wywołać next() (dokładnie jeden raz), aby wskazać, że odpowiedź nie została jeszcze uregulowana lub MUSI rozstrzygnąć odpowiedź, dzwoniąc pod numer res.end() lub za pomocą potoków do odpowiedzi. Nie wolno robić obu tych rzeczy.

Przeanalizowałem kod źródłowy oprogramowania pośredniego (patrz poniżej). Widzimy, że możemy zwrócić false, aby powiedzieć middleware, aby zadzwonić pod numer next(). Oznacza to, że deklarujemy, że ta trasa nie rozstrzygnęła odpowiedzi i chcielibyśmy, aby inne oprogramowanie pośrednie wykonało swoją pracę.

Albo możemy wrócić nazwę szablonu, tekst, tablicę [status, text] lub tablicę [status, headers, text] i middleware będzie rozstrzygać odpowiedź w naszym imieniu przez wywołanie res.end() używając danych wróciliśmy.

Jednakże, poprzez wysłanie do odpowiedzi, już rozstrzygnęliśmy odpowiedź. Router Meteor nie powinien wywoływać next() ani res.end().

Rozwiązaliśmy problem przez rozwidlenie routera Meteor i dokonanie niewielkiej zmiany. Mamy zastąpiła else w wierszu 87 (po if (output === false)) przez:

else if (typeof(output)!="undefined") { 

Zobacz popełnić z sha 8d8fc23d9c w moim widelcem.

W ten sposób return; w metodzie trasy pokaże routerowi, aby wykonał nic. Oczywiście już załatałeś odpowiedź, przesyłając do niego.


Kod źródłowy middleware jak w commit z sha f910a090ae:

// hook up the serving 
__meteor_bootstrap__.app 
    .use(connect.query()) // <- XXX: we can probably assume accounts did this 
    .use(this._config.requestParser(this._config.bodyParser)) 
    .use(function(req, res, next) { 
    // need to wrap in a fiber in case they do something async 
    // (e.g. in the database) 
    if(typeof(Fiber)=="undefined") Fiber = Npm.require('fibers'); 

    Fiber(function() { 
     var output = Meteor.Router.match(req, res); 

     if (output === false) { 
     return next(); 
     } else { 
     // parse out the various type of response we can have 

     // array can be 
     // [content], [status, content], [status, headers, content] 
     if (_.isArray(output)) { 
      // copy the array so we aren't actually modifying it! 
      output = output.slice(0); 

      if (output.length === 3) { 
      var headers = output.splice(1, 1)[0]; 
      _.each(headers, function(value, key) { 
       res.setHeader(key, value); 
      }); 
      } 

      if (output.length === 2) { 
      res.statusCode = output.shift(); 
      } 

      output = output[0]; 
     } 

     if (_.isNumber(output)) { 
      res.statusCode = output; 
      output = ''; 
     } 

     return res.end(output); 
     } 
    }).run(); 
    }); 
+0

** UWAGA **: Tymczasem Meteor 6.5 jest wyłączony i mój zmieniony router przestał działać. – nalply