Node.js Загрузить файл с использованием Content Disposition в качестве имени файла

Я использую модуль 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
});

Но разве это не отрицает преимущества использования канала, а не загружает загруженный файл в память?

Ответы

Ответ 1

Я запрашиваю изображение из yahoo и не использует заголовок content-disposition, но я извлекаю заголовки date и content-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]));

});

Игнорируйте мой выбор изображения, пожалуйста:)

Ответ 2

Вопрос был около того, но сегодня я столкнулся с той же проблемой и решил ее по-другому:

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

Ответ 3

Здесь мое решение:

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.

Надеюсь, это поможет кому-то другому, кто испытывает те же проблемы, что и я.