Node.js将同一可读流传输到多个(可写)目标中


76

我需要连续运行两个命令,这些命令需要从同一流中读取数据。将流传输到另一个流后,缓冲区将被清空,因此我无法再次从该流读取数据,因此无法正常工作:

var spawn = require('child_process').spawn;
var fs = require('fs');
var request = require('request');

var inputStream = request('http://placehold.it/640x360');
var identify = spawn('identify',['-']);

inputStream.pipe(identify.stdin);

var chunks = [];
identify.stdout.on('data',function(chunk) {
  chunks.push(chunk);
});

identify.stdout.on('end',function() {
  var size = getSize(Buffer.concat(chunks)); //width
  var convert = spawn('convert',['-','-scale',size * 0.5,'png:-']);
  inputStream.pipe(convert.stdin);
  convert.stdout.pipe(fs.createWriteStream('half.png'));
});

function getSize(buffer){
  return parseInt(buffer.toString().split(' ')[2].split('x')[0]);
}

请求对此抱怨

Error: You cannot pipe after data has been emitted from the response.

和改变的inputStreamfs.createWriteStream收益率,当然同样的问题。我不想写入文件,而是以某种方式重用请求产生的流(或与此相关的任何其他流)。

一旦完成流式传输,是否可以重用可读流?完成上述示例的最佳方法是什么?


似乎您正在使用imagemick。您可以将50%之类的值传递给-scale以进行缩放。您还可以使用npmjs.org/package/gm
user568109 2013年

2
@ user568109是的。这不是这里的问题。这是一个更笼统的问题...它
很像

Answers:


83

您必须通过将其输送到两个流中来创建流的副本。您可以使用PassThrough流创建一个简单的流,它只是将输入传递到输出。

const spawn = require('child_process').spawn;
const PassThrough = require('stream').PassThrough;

const a = spawn('echo', ['hi user']);
const b = new PassThrough();
const c = new PassThrough();

a.stdout.pipe(b);
a.stdout.pipe(c);

let count = 0;
b.on('data', function (chunk) {
  count += chunk.length;
});
b.on('end', function () {
  console.log(count);
  c.pipe(process.stdout);
});

输出:

8
hi user

5
将此技术与Haraka邮件服务器附件挂钩一起使用,可以将传入流通过管道传递到多个邮件帐户数据库中。这个答案有效。

17
请注意,仅当生成的命令输出的字节数未填充反压缓冲区时,此技术才有效。您可以尝试使用= spawn('head',['-c','200K','/ dev / urandom']);使其失败。如果没有将c输出,则在某个时候,a.stdout将暂停输出。b会耗尽,永远不会结束。
杰罗姆·瓦格纳

44
我很困惑,您说您不能处理相同的流两次,但是您的解决方案是..处理相同的流两次(使用PassThrough转换)。这似乎是矛盾的。标准输出流有什么特别之处吗?
BT

7
我对此进行了测试,它当然可以工作。我认为对您说“您不能两次处理相同的流”是不正确的,因为这就是您正在做的事情。您最初关于无法在流的“结束”之后传送流的陈述是适当的原因。
BT

6
不要使用此方法,因为如果以不同的速率读取流,则会产生问题。试试这个,npmjs.com / package / read-stream-clone对我来说效果很好。
kiwicomb123 '18

12

仅当流花费大致相同的时间来处理数据时,第一个答案才有效。如果花费的时间长得多,则速度较快的数据将请求新数据,因此将覆盖速度较慢的数据仍在使用的数据(在尝试使用重复流解决数据后出现了这个问题)。

以下模式对我来说非常有效。它使用基于Stream2流,Streamz和Promises的库来通过回调同步异步流。使用第一个答案中的熟悉示例:

spawn = require('child_process').spawn;
pass = require('stream').PassThrough;
streamz = require('streamz').PassThrough;
var Promise = require('bluebird');

a = spawn('echo', ['hi user']);
b = new pass;
c = new pass;   

a.stdout.pipe(streamz(combineStreamOperations)); 

function combineStreamOperations(data, next){
  Promise.join(b, c, function(b, c){ //perform n operations on the same data
  next(); //request more
}

count = 0;
b.on('data', function(chunk) { count += chunk.length; });
b.on('end', function() { console.log(count); c.pipe(process.stdout); });

哪一部分实际上是覆盖数据?覆盖的代码自然应该引发错误。
罗伯·西默

2

如何不同时管道输送到两个或多个流中呢?

例如 :

var PassThrough = require('stream').PassThrough;
var mybiraryStream = stream.start(); //never ending audio stream
var file1 = fs.createWriteStream('file1.wav',{encoding:'binary'})
var file2 = fs.createWriteStream('file2.wav',{encoding:'binary'})
var mypass = PassThrough
mybinaryStream.pipe(mypass)
mypass.pipe(file1)
setTimeout(function(){
   mypass.pipe(file2);
},2000)

上面的代码没有产生任何错误,但是file2为空


在某种程度上,它可以帮助我!
Sandip

5
我认为您已经确定了一个问题,但这很令人困惑,因为这不是答案。
迈克尔

1

对于一般问题,以下代码可以正常工作

var PassThrough = require('stream').PassThrough
a=PassThrough()
b1=PassThrough()
b2=PassThrough()
a.pipe(b1)
a.pipe(b2)
b1.on('data', function(data) {
  console.log('b1:', data.toString())
})
b2.on('data', function(data) {
  console.log('b2:', data.toString())
})
a.write('text')

1

我有一个不同的解决方案来同时写入两个流,自然,写入时间将是两个时间的总和,但是我用它来响应下载请求,在此我要保留下载文件的副本。我的服务器(实际上,我使用S3备份,因此我在本地缓存了最常用的文件,以避免多次文件传输)

/**
 * A utility class made to write to a file while answering a file download request
 */
class TwoOutputStreams {
  constructor(streamOne, streamTwo) {
    this.streamOne = streamOne
    this.streamTwo = streamTwo
  }

  setHeader(header, value) {
    if (this.streamOne.setHeader)
      this.streamOne.setHeader(header, value)
    if (this.streamTwo.setHeader)
      this.streamTwo.setHeader(header, value)
  }

  write(chunk) {
    this.streamOne.write(chunk)
    this.streamTwo.write(chunk)
  }

  end() {
    this.streamOne.end()
    this.streamTwo.end()
  }
}

然后,您可以将其用作常规OutputStream

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

并将其传递到您的方法,就像它是响应或fileOutputStream


1

如果您对PassThrough流进行了异步操作,则此处发布的答案将不起作用。适用于异步操作的解决方案包括缓冲流内容,然后根据缓冲结果创建流。

  1. 要缓冲结果,您可以使用concat-stream

    const Promise = require('bluebird');
    const concat = require('concat-stream');
    const getBuffer = function(stream){
        return new Promise(function(resolve, reject){
            var gotBuffer = function(buffer){
                resolve(buffer);
            }
            var concatStream = concat(gotBuffer);
            stream.on('error', reject);
            stream.pipe(concatStream);
        });
    }
    
  2. 要从缓冲区创建流,可以使用:

    const { Readable } = require('stream');
    const getBufferStream = function(buffer){
        const stream = new Readable();
        stream.push(buffer);
        stream.push(null);
        return Promise.resolve(stream);
    }
    

1

您可以使用我创建的这个小npm软件包:

readable-stream-clone

这样,您可以根据需要多次重复使用可读流

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.