使用Node.js进行多个文件流的管道传输

4
我想要将多个文件依次流式传输到浏览器。比如,有多个 CSS 文件,需要将它们连接在一起作为一个文件发送到客户端。
我使用的代码如下:
var directory = path.join(__dirname, 'css');
fs.readdir(directory, function (err, files) {
  async.eachSeries(files, function (file, callback) {
    if (!endsWith(file, '.css')) { return callback(); } // (1)

    var currentFile = path.join(directory, file);
    fs.stat(currentFile, function (err, stats) {
      if (stats.isDirectory()) { return callback(); } // (2)

      var stream = fs.createReadStream(currentFile).on('end', function () {
        callback(); // (3)
      });
      stream.pipe(res, { end: false }); // (4)
    });
  }, function () {
    res.end(); // (5)
  });
});

这个想法是,

  1. 筛选掉没有文件扩展名.css的所有文件。
  2. 筛选掉所有目录。
  3. 一旦一个文件完全被读取,就继续处理下一个文件。
  4. 将每个文件传输到响应流中,但不关闭它。
  5. 在所有文件被传输后结束响应流。

问题在于只有第一个.css文件被传输,而所有剩余的文件都不见了。好像(3)在第一个(4)之后直接跳到了(5)。

有趣的是,如果我把第(4)行替换成:

stream.on('data', function (data) {
  console.log(data.toString('utf8'));
});

一切都按预期工作:我看到了多个文件。如果我将此代码更改为:
stream.on('data', function (data) {
  res.write(data.toString('utf8'));
});

除了第一个文件,所有的文件都再次丢失了。

我做错了什么?

PS:使用Node.js 0.8.7和0.8.22都会出现此错误。

更新

如果您按以下方式更改代码,则可以解决该问题:

var directory = path.join(__dirname, 'css');
fs.readdir(directory, function (err, files) {
  var concatenated = '';
  async.eachSeries(files, function (file, callback) {
    if (!endsWith(file, '.css')) { return callback(); }

    var currentFile = path.join(directory, file);
    fs.stat(currentFile, function (err, stats) {
      if (stats.isDirectory()) { return callback(); }

      var stream = fs.createReadStream(currentFile).on('end', function () {
        callback();
      }).on('data', function (data) { concatenated += data.toString('utf8'); });
    });
  }, function () {
    res.write(concatenated);
    res.end();
  });
});

但是:为什么?为什么不能多次调用res.write而不是先将所有块求和,然后一次性写入?


2
你得到的结果并不是你所要求的,因为你实际上没有进行管道操作,而是在缓冲区中写入数据,然后将缓冲区写入输出流。这也是你额外问题的答案:如果你真的将数据传输到输出流中,你就不必担心write()只能被调用一次。你应该通过查看这些Streams 2示例来找到一个管道解决方案:https://github.com/Floby。 特别是这个: https://github.com/Floby/node-catstream - vanthome
我知道我没有在我写的内容中使用管道,但我已经找到了一种使用管道的解决方案。正如您可以从下面的答案中看到的那样,问题不在代码中,而是在单元测试中。 - Golo Roden
3个回答

3
考虑使用multistream,它可以让你将多个流合并并依次发出。

2

代码本身是没有问题的,只是单元测试有误...

已经修复了,现在它像魔法一样顺畅运行 :-)


我正在尝试对图像和音频文件执行相同的操作。但是,虽然响应有效负载的总和表明已接收所有文件,但我只能看到第一个图像(在Postman中)。当多个文件在一个流中被传输时,如何区分它们? - Amirhosein Al

1

可能会帮助其他人:

const fs = require("fs");
const pth = require("path");

let readerStream1 = fs.createReadStream(pth.join(__dirname, "a.txt"));
let readerStream2 = fs.createReadStream(pth.join(__dirname, "b.txt"));
let writerStream = fs.createWriteStream(pth.join(__dirname, "c.txt"));

//only readable streams have "pipe" method
readerStream1.pipe(writerStream);
readerStream2.pipe(writerStream);

我也检查了 Rocco 的答案,它运行得很好。
//npm i --save multistream

const multi = require('multistream');
const fs = require('fs');
const pth = require("path");
 
let streams = [
  fs.createReadStream(pth.join(__dirname, "a.txt")),
  fs.createReadStream(pth.join(__dirname, "b.txt"))
];
let writerStream = fs.createWriteStream(pth.join(__dirname, "c.txt"));
 
//new multi(streams).pipe(process.stdout);
new multi(streams).pipe(writerStream);

并将结果发送给客户端:
const multi = require('multistream');
const fs = require('fs');
const pth = require("path");
const exp = require("express");
const app = exp();
app.listen(3000);

app.get("/stream", (q, r) => {
  new multi([
    fs.createReadStream(pth.join(__dirname, "a.txt")),
    fs.createReadStream(pth.join(__dirname, "b.txt"))
  ]).pipe(r);
});

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