Node.js Piping cùng một luồng có thể đọc thành nhiều mục tiêu (có thể ghi)


77

Tôi cần chạy hai lệnh nối tiếp cần đọc dữ liệu từ cùng một luồng. Sau khi chuyển một luồng vào một luồng khác, bộ đệm bị trống vì vậy tôi không thể đọc lại dữ liệu từ luồng đó nên điều này không hoạt động:

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

Yêu cầu phàn nàn về điều này

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

và thay đổi inputStream để fs.createWriteStreamtạo ra cùng một vấn đề tất nhiên. Tôi không muốn ghi vào tệp nhưng sử dụng lại theo một cách nào đó luồng mà yêu cầu tạo ra (hoặc bất kỳ cách nào khác cho vấn đề đó).

Có cách nào để sử dụng lại luồng có thể đọc được sau khi hoàn thành đường ống không? Cách tốt nhất để đạt được điều gì đó giống như ví dụ trên là gì?


Có vẻ như bạn đang sử dụng imagemick. Bạn có thể chuyển giá trị như 50% thành -scale để chia tỷ lệ. Bạn cũng có thể sử dụng npmjs.org/package/gm
user568109

2
@ user568109 Vâng. Đó không phải là vấn đề ở đây. Đó là một câu hỏi tổng quát hơn ... đó là ImageMagick vì nó có thể là bất kỳ lệnh khác / dòng
Maroshii

Câu trả lời:


83

Bạn phải tạo bản sao của luồng bằng cách chuyển nó thành hai luồng. Bạn có thể tạo một luồng đơn giản với một luồng PassThrough, nó chỉ đơn giản là chuyển đầu vào cho đầu ra.

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

Đầu ra:

8
hi user

5
Đã sử dụng kỹ thuật này với các móc đính kèm của máy chủ thư Haraka để chuyển luồng đến vào nhiều cơ sở dữ liệu tài khoản thư. Câu trả lời này hoạt động.

17
Lưu ý rằng kỹ thuật này chỉ hoạt động nếu lệnh được tạo ra xuất ra một số byte không lấp đầy bộ đệm áp suất ngược. bạn có thể thử nhưng không thành công với a = spawn ('head', ['-c', '200K', '/ dev / urandom']) ;. Nếu c không được đưa ra, tại một thời điểm nào đó, a.stdout sẽ tạm dừng đường ống ra. b sẽ tiêu và không bao giờ hết.
Jerome WAGNER

44
Tôi bối rối, bạn nói rằng bạn không thể xử lý cùng một luồng hai lần, nhưng giải pháp của bạn là .. xử lý cùng một luồng hai lần (với biến đổi PassThrough). Điều này có vẻ mâu thuẫn. Đây có phải là điều gì đó đặc biệt về các luồng stdout không?
BT

7
Tôi đã thử nghiệm điều này và nó chắc chắn hoạt động. Tôi nghĩ rằng việc bạn nói "bạn không thể xử lý cùng một [luồng] hai lần" là không đúng, vì đó là điều bạn đang làm. Tuyên bố đầu tiên của bạn về việc không thể truyền một luồng sau khi 'kết thúc' là lý do thích hợp.
BT

6
Không sử dụng phương pháp này vì nó tạo ra sự cố nếu các luồng được đọc ở các tốc độ khác nhau. Hãy thử điều này thay vì npmjs.com/package/readable-stream-clone hoạt động tốt với tôi.
kiwicomb123

12

Câu trả lời đầu tiên chỉ hoạt động nếu các luồng mất khoảng thời gian tương đương để xử lý dữ liệu. Nếu mất nhiều thời gian hơn, dữ liệu nhanh hơn sẽ yêu cầu dữ liệu mới, do đó ghi đè lên dữ liệu vẫn đang được sử dụng bởi dữ liệu chậm hơn (tôi gặp sự cố này sau khi cố gắng giải quyết nó bằng cách sử dụng một luồng trùng lặp).

Mô hình sau đây hoạt động rất tốt đối với tôi. Nó sử dụng một thư viện dựa trên các luồng Stream2, Streamz và Promises để đồng bộ hoá các luồng không đồng bộ thông qua một cuộc gọi lại. Sử dụng ví dụ quen thuộc từ câu trả lời đầu tiên:

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

Phần nào thực sự đang ghi đè dữ liệu? Mã ghi đè tự nhiên sẽ gây ra lỗi.
Robert Siemer

1

Đối với vấn đề chung, mã sau hoạt động tốt

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

Điều gì về đường ống vào hai hoặc nhiều dòng không cùng một lúc?

Ví dụ :

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)

Đoạn mã trên không tạo ra bất kỳ lỗi nào nhưng tệp 2 trống


theo một cách nào đó nó giúp tôi!
sandip

5
tôi nghĩ bạn đã xác định được vấn đề, nhưng nó khó hiểu vì đây không phải là câu trả lời.
Michael

1

Tôi có một giải pháp khác để ghi vào hai luồng đồng thời, đương nhiên, thời gian ghi sẽ là phép cộng của hai lần, nhưng tôi sử dụng giải pháp này để phản hồi yêu cầu tải xuống, trong đó tôi muốn giữ một bản sao của tệp đã tải xuống. máy chủ của tôi (thực sự tôi sử dụng bản sao lưu S3, vì vậy tôi lưu vào bộ đệm cục bộ các tệp được sử dụng nhiều nhất để tránh chuyển nhiều tệp)

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

Sau đó, bạn có thể sử dụng nó như một OutputStream thông thường

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

và chuyển nó đến phương thức của bạn như thể nó là một phản hồi hoặc một tệpOutputStream


1

Nếu bạn có thao tác không đồng bộ trên luồng PassThrough, câu trả lời được đăng ở đây sẽ không hoạt động. Một giải pháp phù hợp với các hoạt động không đồng bộ bao gồm đệm nội dung luồng và sau đó tạo luồng từ kết quả được lưu vào bộ đệm.

  1. Để đệm kết quả, bạn có thể sử dụng 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. Để tạo luồng từ bộ đệm, bạn có thể sử dụng:

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

1

Bạn có thể sử dụng gói npm nhỏ này mà tôi đã tạo:

readable-stream-clone

Với điều này, bạn có thể sử dụng lại các luồng có thể đọc nhiều lần nếu bạn cần

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.