developer tip

Node.js 동일한 읽기 가능한 스트림을 여러 (쓰기 가능한) 대상으로 파이핑

copycodes 2020. 11. 21. 10:30
반응형

Node.js 동일한 읽기 가능한 스트림을 여러 (쓰기 가능한) 대상으로 파이핑


동일한 스트림에서 데이터를 읽어야하는 두 개의 명령을 직렬로 실행해야합니다. 스트림을 다른 스트림으로 파이프 한 후 버퍼가 비워져 해당 스트림에서 데이터를 다시 읽을 수 없으므로 작동하지 않습니다.

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.

물론 inputStream변경하면 fs.createWriteStream동일한 문제가 발생합니다. 나는 파일에 쓰고 싶지 않지만 요청이 생성 하는 스트림 (또는 그 문제에 대해 다른 것) 을 어떤 방식 으로든 재사용 하고 싶습니다 .

배관이 끝나면 읽을 수있는 스트림을 재사용 할 수있는 방법이 있습니까? 위의 예와 같은 작업을 수행하는 가장 좋은 방법은 무엇입니까?


두 개의 하천으로 배관하여 하천의 복제본을 만들어야합니다. 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

첫 번째 대답은 스트림이 데이터를 처리하는 데 대략 같은 시간이 걸리는 경우에만 작동합니다. 훨씬 더 오래 걸리면 더 빠른 데이터가 새 데이터를 요청하여 결과적으로 느린 데이터가 여전히 사용중인 데이터를 덮어 씁니다 (중복 스트림을 사용하여 해결하려고 시도한 후이 문제가 발생했습니다).

다음 패턴은 저에게 매우 효과적이었습니다. 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); });

동시에 두 개 이상의 하천으로 배관하는 것은 어떻습니까?

예 :

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는 비어 있습니다.


For general problem, the following code works fine

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')

If you have async operations on the PassThrough streams, the answers posted here won't work. A solution that works for async operations includes buffering the stream content and then creating streams from the buffered result.

  1. To buffer the result you can use 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. To create streams from the buffer you can use:

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

I have a different solution to write to two streams simultaneously, naturally, the time to write will be the addition of the two times, but I use it to respond to a download request, where I want to keep a copy of the downloaded file on my server (actually I use a S3 backup, so I cache the most used files locally to avoid multiple file transfers)

/**
 * 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()
  }
}

You can then use this as a regular OutputStream

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

and pass it to to your method as if it was a response or a fileOutputStream

참고URL : https://stackoverflow.com/questions/19553837/node-js-piping-the-same-readable-stream-into-multiple-writable-targets

반응형