web-dev-qa-db-fra.com

Node.js Télécharger un fichier en utilisant la disposition de contenu comme nom de fichier

J'utilise le module Request pour télécharger des fichiers, mais je ne sais pas trop comment diriger la réponse vers un flux de sortie lorsque le nom de fichier doit provenir de l'en-tête "Content-Disposition". Donc, fondamentalement, je dois lire la réponse jusqu'à ce que l'en-tête soit trouvé, puis diriger le reste vers ce nom de fichier.

Les exemples montrent quelque chose comme:

request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png'));

Où je veux faire (pseudocode):

var req = request('http://example.com/download_latest_version?token=XXX');
var filename = req.response.headers['Content-Disposition'];

req.pipe(fs.createWriteStream(filename));

Je pourrais obtenir le nom de fichier en utilisant le rappel de demande:

request(url, function(err, res, body) {
 // get res headers here
});

Mais cela n'annulerait-il pas les avantages de l'utilisation de pipe et de ne pas charger le fichier téléchargé en mémoire?

24
user3019326

Je demande une image à Yahoo et elle n'utilise pas le content-disposition en-tête mais j'extrait les date et content-type en-têtes pour construire un nom de fichier. Cela semble assez proche de ce que vous essayez de faire ...

var request = require('request'),
fs = require('fs');

var url2 = 'http://l4.yimg.com/nn/fp/rsz/112113/images/smush/aaroncarter_635x250_1385060042.jpg';

var r = request(url2);

r.on('response',  function (res) {
  res.pipe(fs.createWriteStream('./' + res.headers.date + '.' + res.headers['content-type'].split('/')[1]));

});

Ignorez mon choix d'image s'il vous plaît :)

31
kberg

La question existe depuis un certain temps, mais j'ai aujourd'hui rencontré le même problème et l'ai résolu différemment:

var Request = require( 'request' ),
    Fs = require( 'fs' );

// RegExp to extract the filename from Content-Disposition
var regexp = /filename=\"(.*)\"/gi;

// initiate the download
var req = Request.get( 'url.to/somewhere' )
                 .on( 'response', function( res ){

                    // extract filename
                    var filename = regexp.exec( res.headers['content-disposition'] )[1];

                    // create file write stream
                    var fws = Fs.createWriteStream( '/some/path/' + filename );

                    // setup piping
                    res.pipe( fws );

                    res.on( 'end', function(){
                      // go on with processing
                    });
                 });
13
Sirko

Voici ma solution:

var fs = require('fs');
var request = require('request');
var through2 = require('through2');

var req = request(url);
req.on('error', function (e) {
    // Handle connection errors
    console.log(e);
});
var bufferedResponse = req.pipe(through2(function (chunk, enc, callback) {
    this.Push(chunk);
    callback()
}));
req.on('response', function (res) {
    if (res.statusCode === 200) {
        try {
            var contentDisposition = res.headers['content-disposition'];
            var match = contentDisposition && contentDisposition.match(/(filename=|filename\*='')(.*)$/);
            var filename = match && match[2] || 'default-filename.out';
            var dest = fs.createWriteStream(filename);
            dest.on('error', function (e) {
                // Handle write errors
                console.log(e);
            });
            dest.on('finish', function () {
                // The file has been downloaded
                console.log('Downloaded ' + filename);
            });
            bufferedResponse.pipe(dest);
        } catch (e) {
            // Handle request errors
            console.log(e);
        }
    }
    else {
        // Handle HTTP server errors
        console.log(res.statusCode);
    }
});

Les autres solutions publiées ici utilisent res.pipe, qui peut échouer si le contenu est transféré à l'aide du codage gzip, car le flux de réponse contient les données HTTP brutes (compressées). Pour éviter ce problème, vous devez utiliser request.pipe au lieu. (Voir le deuxième exemple sur https://github.com/request/request#examples .)

Lors de l'utilisation de request.pipe J'obtenais une erreur: "Vous ne pouvez pas diriger après que les données ont été émises à partir de la réponse.", Parce que je faisais des trucs asynchrones avant de piper (créer un répertoire pour contenir le fichier téléchargé). J'ai également eu quelques problèmes où le fichier était écrit sans contenu, ce qui pourrait être dû à request en lisant la réponse HTTP et en la mettant en mémoire tampon.

J'ai donc fini par créer un flux de mise en mémoire tampon intermédiaire avec through2, afin que je puisse lui rediriger la demande avant le déclenchement du gestionnaire de réponse, puis réacheminer le flux de mise en mémoire tampon vers le flux de fichiers une fois le nom de fichier connu.

Enfin, j'analyse l'en-tête de disposition de contenu, que le nom de fichier soit codé en clair ou en format UTF-8 en utilisant le filename*=''file.txt syntaxe.

J'espère que cela aide quelqu'un d'autre qui éprouve les mêmes problèmes que moi.

4
chris