2013-06-12 16 views
5

Devo consentire all'utente della mia app di scaricare un file con Meteor. Attualmente quello che faccio è quando l'utente richiede di scaricare un file che inserisco in una raccolta "fileRequests" in Mongo un documento con il percorso del file e un timestamp della richiesta e restituire l'ID della richiesta appena creata. Quando il cliente riceve il nuovo ID, passa immediatamente a mydomain.com/uploads/:id. Ho quindi utilizzare qualcosa di simile per intercettare la richiesta prima Meteor fa:Come utilizzare createReadStream di filesystem con il router 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(); 
    }); 

Posso controllare per assicurarsi che il file di richiesta è stata fatta meno di 5 secondi fa, e ho subito cancellare il documento di richiesta dopo che ho interrogato esso .

Questo funziona, ed è sicuro (abbastanza) penso. Nessuno può effettuare una richiesta senza aver effettuato l'accesso e 5 secondi è una finestra piuttosto piccola per consentire a qualcuno di essere in grado di bloccare l'URL della richiesta creata ma semplicemente non mi sento a posto con la mia soluzione. Sembra sporco!

Quindi ho tentato di utilizzare Meteor-Router per eseguire la stessa operazione. In questo modo posso controllare se sono loggati correttamente senza fare il 5 secondo aperto agli inganni mondiali.

Quindi, ecco il codice che ho scritto per questo:

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

Questo sembra grande, si adatta a destra dentro con il resto del codice ed è di facile lettura, senza l'hacking coinvolto, MA! Non funziona! Il browser gira e gira e non sa mai cosa fare. Ho dei messaggi di errore ZERO in arrivo. Posso continuare a utilizzare l'app su altre schede. Non so cosa stia facendo, non si ferma mai "in caricamento". Se riavvio il server, ottengo un file da 0 byte con tutte le intestazioni corrette, ma non ottengo i dati.

Qualsiasi aiuto è molto apprezzato !!

EDIT:

Dopo scavato un po 'di più, ho notato che cercando di trasformare l'oggetto risposta in un oggetto JSON generato un errore struttura circolare.

Ora la cosa interessante di questo è che quando ascolto il filestream per l'evento "dati" e provo a stringificare l'oggetto risposta, non ottengo quell'errore. Ma se provo a fare la stessa cosa nella mia prima soluzione (ascoltare "dati" e stringificare la risposta) ottengo di nuovo l'errore.

Così utilizzando la soluzione Meteor-Router qualcosa sta accadendo all'oggetto risposta. Ho anche notato che sull'evento "data" response.finished è contrassegnato come true.

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

Abbiamo lo stesso problema. Abbiamo scoperto che ci sono due problemi in uno. Devi fare 'return false;', questo richiama il middleware 'next()' qui: https://github.com/tmeasday/meteor-router/blob/master/lib/router_server.js#L86, ma 'pipe() 'non funziona, anche. Stiamo ancora indagando. – nalply

risposta

1

Il router Meteor installa un middleware per eseguire il routing. Tutti i middleware Connect DEVONO chiamare lo next() (esattamente una volta) per indicare che la risposta non è stata ancora risolta o DEVE risolvere la risposta chiamando res.end() o piping alla risposta. Non è permesso fare entrambe le cose.

Ho studiato il codice sorgente del middleware (vedi sotto). Vediamo che possiamo restituire false per comunicare al middleware di chiamare next(). Ciò significa che dichiariamo che questa rotta non ha regolato la risposta e vorremmo lasciare che altri middleware facciano il loro lavoro.

Oppure possiamo restituire un nome di modello, un testo, una matrice o una matrice [status, text][status, headers, text], e il middleware sistemeremo la risposta per nostro conto chiamando res.end() utilizzando i dati siamo tornati.

Tuttavia, collegando la risposta, abbiamo già risolto la risposta. Il router Meteor non deve chiamare next()res.end().

Abbiamo risolto il problema biforcando il router Meteor e apportando una piccola modifica. Abbiamo sostituito il else in linea 87 (dopo if (output === false)) da:

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

Vedere il commit con sha 8d8fc23d9c nella mia forchetta.

In questo modo, return; nel metodo di instradamento dirà al router di fare nulla. Ovviamente hai già risolto la risposta collegandoti.


codice sorgente del middleware come nel commettere con 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

** NOTE **: Nel frattempo, Meteor 6.5 è spento e il mio router modificato non funziona più. – nalply

Problemi correlati