使用内容描述下载文件的Node.js文件名

30

我正在使用Request模块下载文件,但我不太确定如何将响应导流到输出流中,当文件名必须来自"Content-Disposition"头时。基本上,我需要读取响应直到找到头文件,然后将其余部分传输到该文件名。

示例显示了以下内容:

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

我想要执行以下伪代码:

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

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

我可以使用 Request 回调函数获取文件名:

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

但这样做不会抵消使用管道并避免将下载的文件加载到内存中的好处吗?

3个回答

32

我正在从雅虎请求一张图片,但它没有使用content-disposition头部,我正在提取datecontent-type头部来构造文件名。这似乎足够接近你尝试做的事情...

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

});

请忽略我的图片选择 :)


哇,我忘记了亚伦·卡特。这让我重新考虑贾斯汀·比伯的事情...市场营销! - moeiscool

13

这个问题已经存在一段时间了,但我今天遇到了同样的问题,并采用了不同的解决方法:

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

1
res.on('end' 似乎触发过早,我认为你应该使用 Request.get(..).on('response', ..).on('finish'。请参考 https://dev59.com/RGgu5IYBdhLWcg3wRlFh#11448311。 - Markus Hedlund

4
这是我的解决方案:
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);
    }
});

这里其他的解决方案使用了res.pipe,但如果内容使用gzip编码传输,则可能会失败,因为响应流包含原始(压缩)HTTP数据。为避免此问题,您必须改用request.pipe(请参见https://github.com/request/request#examples中的第二个示例)。
当使用request.pipe时,我遇到了一个错误:“在响应已发出数据之后,无法进行管道操作”,因为我在实际进行管道操作之前做了一些异步处理(创建一个目录以保存下载的文件)。我还遇到了一些问题,其中文件被写入但是没有内容,这可能是由于request读取HTTP响应并将其缓冲造成的。
因此,我最终使用through2创建了一个中间缓冲流,以便在响应处理程序触发之前将请求导入它,然后在知道文件名后从缓冲流中导入文件流。
最后,无论文件名是以纯文本形式还是UTF-8形式编码,我都会解析内容分配标头,使用filename*=''file.txt语法。
希望这能帮助遇到与我相同问题的其他人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接