2011-01-23 7 views
29

Sto cercando di creare un downloader di file come servizio in background ma quando un file di grandi dimensioni è pianificato, viene prima messo in memoria e quindi, al termine del download il il file è scritto sul disco.Scarica file di grandi dimensioni con node.js evitando l'elevato consumo di memoria

Come posso fare in modo che il file venga scritto in modo graduale sul disco preservando la memoria considerando che potrei avere molti file scaricati nello stesso momento?

Ecco il codice `m utilizzando:

var sys = require("sys"), 
    http = require("http"), 
    url = require("url"), 
    path = require("path"), 
    fs = require("fs"), 
    events = require("events"); 

var downloadfile = "http://nodejs.org/dist/node-v0.2.6.tar.gz"; 

var host = url.parse(downloadfile).hostname 
var filename = url.parse(downloadfile).pathname.split("/").pop() 

var theurl = http.createClient(80, host); 
var requestUrl = downloadfile; 
sys.puts("Downloading file: " + filename); 
sys.puts("Before download request"); 
var request = theurl.request('GET', requestUrl, {"host": host}); 
request.end(); 

var dlprogress = 0; 


setInterval(function() { 
    sys.puts("Download progress: " + dlprogress + " bytes"); 
}, 1000); 


request.addListener('response', function (response) { 
    response.setEncoding('binary') 
    sys.puts("File size: " + response.headers['content-length'] + " bytes.") 
    var body = ''; 
    response.addListener('data', function (chunk) { 
     dlprogress += chunk.length; 
     body += chunk; 
    }); 
    response.addListener("end", function() { 
     fs.writeFileSync(filename, body, 'binary'); 
     sys.puts("After download finished"); 
    }); 

}); 
+0

Qualsiasi possibilità che tu possa condividere il risultato finale ? Sto cercando qualcosa di simile a questo ... – Eli

+0

Ho provato a implementare una funzionalità per seguire i reindirizzamenti 302 ma non penso che funzioni correttamente. Forse potresti provare. Eccolo: https://gist.github.com/1297063 – Carlosedp

risposta

26

ho cambiato la richiamata a:

request.addListener('response', function (response) { 
     var downloadfile = fs.createWriteStream(filename, {'flags': 'a'}); 
     sys.puts("File size " + filename + ": " + response.headers['content-length'] + " bytes."); 
     response.addListener('data', function (chunk) { 
      dlprogress += chunk.length; 
      downloadfile.write(chunk, encoding='binary'); 
     }); 
     response.addListener("end", function() { 
      downloadfile.end(); 
      sys.puts("Finished downloading " + filename); 
     }); 

    }); 

questo ha funzionato perfettamente.

+0

Non dovremmo preferire setEncoding (null) invece di 'binary'? –

+0

'{'flags': 'a'}' aggiungerà i dati al file se già esiste – respectTheCode

1

Invece di tenere il contenuto nella memoria in chi ascolta "data" evento si dovrebbe scrivere sul file in modalità accodamento.

2

Quando si esegue il download di file di grandi dimensioni, utilizzare fs.write e non writeFile poiché sovrascrive il contenuto precedente.

function downloadfile(res) { 
    var requestserver = http.request(options, function(r) { 
     console.log('STATUS: ' + r.statusCode); 
     console.log('HEADERS: ' + JSON.stringify(r.headers)); 

     var fd = fs.openSync('sai.tar.gz', 'w'); 

     r.on('data', function (chunk) { 
      size += chunk.length; 
      console.log(size+'bytes received'); 
      sendstatus(res,size); 
      fs.write(fd, chunk, 0, chunk.length, null, function(er, written) { 
      }); 
     }); 
     r.on('end',function(){ 
      console.log('\nended from server'); 
      fs.closeSync(fd); 
      sendendstatus(res); 
     }); 
    }); 
} 
+1

fs.write non è sicuro se non si attende la richiamata. Dovresti usare un WriteStream. – respectTheCode

+0

Molto meglio solo per pipe 'res' nel flusso di file scrivibile. – Brad

2

il pacchetto di richiesta funziona per gli utilizzi?

ti permette di fare cose come questa:

request(downloadurl).pipe(fs.createWriteStream(downloadtohere)) 
+0

Non hai nemmeno bisogno di una richiesta per questo. Semplicemente pipe 'res' da' http.get' o qualunque cosa venga usata. – Brad

1

Utilizzare i corsi d'acqua come Carter Cole suggerito. Ecco un esempio più completo

var inspect = require('eyespect').inspector(); 
var request = require('request'); 
var filed = require('filed'); 
var temp = require('temp'); 
var downloadURL = 'http://upload.wikimedia.org/wikipedia/commons/e/ec/Hazard_Creek_Kayaker.JPG'; 
var downloadPath = temp.path({prefix: 'singlePageRaw', suffix: '.jpg'}); 

var downloadFile = filed(downloadPath); 
var r = request(downloadURL).pipe(downloadFile); 


r.on('data', function(data) { 
    inspect('binary data received'); 
}); 
downloadFile.on('end', function() { 
    inspect(downloadPath, 'file downloaded to path'); 
}); 

downloadFile.on('error', function (err) { 
    inspect(err, 'error downloading file'); 
}); 

Potrebbe essere necessario installare i moduli di cui si può fare tramite npm install filed request eyespect temp

+1

Non c'è motivo di usare eyespect, archiviato o temp. L'esempio è buono, ma sembra gonfio. –

4

Date un'occhiata a http-request:

// shorthand syntax, buffered response 
http.get('http://localhost/get', function (err, res) { 
    if (err) throw err; 
    console.log(res.code, res.headers, res.buffer.toString()); 
}); 

// save the response to 'myfile.bin' with a progress callback 
http.get({ 
    url: 'http://localhost/get', 
    progress: function (current, total) { 
     console.log('downloaded %d bytes from %d', current, total); 
    } 
}, 'myfile.bin', function (err, res) { 
    if (err) throw err; 
    console.log(res.code, res.headers, res.file); 
}); 
Problemi correlati