【问题标题】:Dynamically create and stream zip to client动态创建 zip 并将其流式传输到客户端
【发布时间】:2013-11-20 21:22:21
【问题描述】:

我正在使用 NodeJs(w/express),我正在尝试将一个 zip 文件流式传输回客户端。 zip 中包含的文件并不存在于文件系统中,而是动态创建的。我想将文件内容流式传输到 zip 并将 zip 流式传输回客户端。

I.E.我希望客户收到:

tmp.zip
 --> 1.txt
 --> 2.txt
 --> 3.txt

其中 1,2,3.txt 是动态创建并流式传输到 zip 文件的。这可能吗?

【问题讨论】:

  • 不清楚您所说的“流式传输到 zip 文件”是什么意思...... HTTP 客户端最终收到什么文件?压缩文件?还是三个文本文件的串联?
  • 对不起,格式搞砸了,希望更清楚。我想寄回一个拉链。我试图解决的两个主要问题。 1. 如何将 zip 流式传输回来,2. 如何将我的 3 个文件流式传输到我正在流式传输的 zip 文件。 IE。我已经看到了流式传输文件系统上已经存在的文件的 zip 的示例,但是如果您正在动态创建文件,则没有如何执行此操作的示例。
  • 你能找到任何解决方法吗?

标签: node.js express streaming zip


【解决方案1】:

Archiver 有一个 append 方法,可让您将文本保存为文件。要将这些数据“流式传输”给用户,您可以简单地通过管道传输到 HTTP 响应对象。

var Http = require('http');
var Archiver = require('archiver');

Http.createServer(function (request, response) {
    // Tell the browser that this is a zip file.
    response.writeHead(200, {
        'Content-Type': 'application/zip',
        'Content-disposition': 'attachment; filename=myFile.zip'
    });

    var zip = Archiver('zip');

    // Send the file to the page output.
    zip.pipe(response);

    // Create zip with some files. Two dynamic, one static. Put #2 in a sub folder.
    zip.append('Some text to go in file 1.', { name: '1.txt' })
        .append('Some text to go in file 2. I go in a folder!', { name: 'somefolder/2.txt' })
        .file('staticFiles/3.txt', { name: '3.txt' })
        .finalize();

}).listen(process.env.PORT);

这将创建一个包含两个文本文件的 zip 文件。访问此页面的用户将看到文件下载提示。

【讨论】:

  • 现在附加所有文件后创建的 zip 文件会发生什么。我有类似的情况,我想允许我的用户一次下载多个文件。由于我无法将它们与响应一起发送。我必须创建一个临时 zip 并在其中移动文件,然后提供它,然后将其删除以存储空间。那么,归档器是创建一个临时压缩文件还是永久压缩文件?
  • 我不认为 zip 文件是“创建”的,因为实用程序正在构建 zip 文件,它会将这些位写入流,这将是对 http 请求的响应。我确定它会在 RAM 的某个地方(或者如果文件很大,则交换文件)中存在一分钟,直到文件被发送到浏览器,然后浏览器会将其保存到最终用户的临时文件中,直到它被保存到一个位置。我已经让这段代码运行了几个月,而且我的磁盘空间使用量保持不变,因此您不需要清除任何临时 zip。
  • Archiver repo 中的这个例子帮助了我。 github.com/ctalkington/node-archiver/blob/master/examples/…
  • 存档库现在似乎没有解压缩/提取功能,所以我必须使用另一个库。
  • 这太棒了。
【解决方案2】:

是的,这是可能的。我建议查看 Streams Playground 以了解 Node Streams 的工作原理。

核心 zlib 库中的 zip 压缩似乎不支持多个文件。如果您想使用 tar-gzip,可以使用 node-tar 对其进行 tar。但如果你想要 ZIP,adm-zip 看起来是最好的选择。另一种可能是node-archiver

更新:

example 展示了如何使用支持流的 Archiver。只需将fs.createReadStream 替换为您动态创建的流,并将output 流到Express 的res 而不是fs.createWriteStream

var fs = require('fs');

var archiver = require('archiver');

var output = fs.createWriteStream(__dirname + '/example-output.zip');
var archive = archiver('zip');

output.on('close', function() {
  console.log('archiver has been finalized and the output file descriptor has closed.');
});

archive.on('error', function(err) {
  throw err;
});

archive.pipe(output);

var file1 = __dirname + '/fixtures/file1.txt';
var file2 = __dirname + '/fixtures/file2.txt';

archive
  .append(fs.createReadStream(file1), { name: 'file1.txt' })
  .append(fs.createReadStream(file2), { name: 'file2.txt' });

archive.finalize(function(err, bytes) {
  if (err) {
    throw err;
  }

  console.log(bytes + ' total bytes');
});

【讨论】:

  • 令人困惑的部分是我不知道如何使用 adm-zip(或任何其他 zip 模块)将 zip 写入流而不是文件系统上的文件。更不用说将文件流式传输到 zip,然后流式传输到客户端。
  • 我为你添加了一个例子。
  • 看过那个例子。仍然无法得到我需要工作的东西。我正在动态创建 file1 和 file2(它们不在文件系统上)。当我为这些文件创建数据时,我想将其添加到流中。但我希望它在添加时被压缩。
  • 我会建议一个新问题,您可以在其中发布测试代码和您看到的结果。
【解决方案3】:

解决方案:express.js、wait.for、zip-stream

app.get('/api/box/:box/:key/download', function (req, res) {

    var wait = require('wait.for');

    var items = wait.for(function (next) {
        BoxItem.find({box: req.Box}).exec(next)
    });

    res.set('Content-Type', 'application/zip');
    res.set('Content-Disposition', 'attachment; filename=' + req.Box.id + '.zip');

    var ZipStream = require('zip-stream');
    var zip = new ZipStream();

    zip.on('error', function (err) {
        throw err;
    });

    zip.pipe(res);

    items.forEach(function (item) {

        wait.for(function (next) {

            var path = storage.getItemPath(req.Box, item);
            var source = require('fs').createReadStream(path);

            zip.entry(source, { name: item.name }, next);
        })

    });

    zip.finalize();

});

【讨论】:

  • @ZachB 我不认为它变慢了,因为模块被缓存了。
【解决方案4】:

使用 expressjs 和 node-zip 将 zip 文件作为二进制数据发送:

app.get("/multipleinzip", (req, res) => {
    var zip = new require('node-zip')();
    var csv1 = "a,b,c,d,e,f,g,h\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8";
    zip.file('test1.file', csv1);
    var csv2 = "z,w,x,d,e,f,g,h\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8";
    zip.file('test2.file', csv2);
    var csv3 = "q,w,e,d,e,f,g,h\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8";
    zip.file('test3.file', csv3);
    var csv4 = "t,y,u,d,e,f,g,h\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8\n1,2,3,4,5,6,7,8";
    zip.file('test4.file', csv4);
    var data = zip.generate({base64:false,compression:'DEFLATE'});
    console.log(data); // ugly data
    res.type("zip")
    res.send(new Buffer(data, 'binary'));
})

为 zip 文件创建下载链接。使用 ->

获取数据并将响应转换为数组缓冲区
    //get the response from fetch as arrayBuffer...
    var data = response.arrayBuffer();

    const blob = new Blob([data]);
    const fileName = `${filename}.${extension}`;
    
    if (navigator.msSaveBlob) {
      // IE 10+
      navigator.msSaveBlob(blob, fileName);
    } else {
      const link = document.createElement('a');
      // Browsers that support HTML5 download attribute
      if (link.download !== undefined) {
        const url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', fileName);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      }
    }

【讨论】:

  • 我们赢了!
猜你喜欢
  • 2017-03-12
  • 2012-08-27
  • 2015-03-13
  • 2015-11-29
  • 1970-01-01
  • 2015-11-28
  • 1970-01-01
  • 2011-11-10
  • 2014-09-26
相关资源
最近更新 更多