web-dev-qa-db-fra.com

Node.js Piping du même flux lisible dans plusieurs cibles (inscriptibles)

Je dois exécuter deux commandes en série qui doivent lire les données du même flux. Après avoir transféré un flux dans un autre, le tampon est vidé afin que je ne puisse plus lire les données de ce flux, cela ne fonctionne donc pas:

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

Demande se plaint à ce sujet

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

et changer le inputStream en fs.createWriteStream donne le même problème, bien sûr. Je ne veux pas écrire dans un fichier mais réutiliser d'une manière ou d'une autre le flux que demande (ou tout autre pour cela importe).

Existe-t-il un moyen de réutiliser un flux lisible une fois la tuyauterie terminée? Quel serait le meilleur moyen d'accomplir quelque chose comme l'exemple ci-dessus?

64
Maroshii

Vous devez créer une copie du flux en le connectant à deux flux. Vous pouvez créer un flux simple avec un flux PassThrough, il passe simplement l'entrée à la sortie.

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

Sortie:

8
hi user
74
user568109

La première réponse ne fonctionne que si les flux prennent à peu près le même temps pour traiter les données. Si l'on prend beaucoup plus de temps, le plus rapide demandera de nouvelles données, écrasant par conséquent les données toujours utilisées par le plus lent (j'ai eu ce problème après avoir essayé de le résoudre en utilisant un flux dupliqué).

Le modèle suivant a très bien fonctionné pour moi. Il utilise une bibliothèque basée sur les flux Stream2, Streamz et Promises pour synchroniser les flux asynchrones via un rappel. En utilisant l'exemple familier de la première réponse:

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

Qu'en est-il de la tuyauterie dans deux ou plusieurs flux pas en même temps?

Par exemple :

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)

Le code ci-dessus ne produit aucune erreur mais le fichier2 est vide

2
user3683370

Pour le problème général, le code suivant fonctionne bien

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
Jake

Si vous avez des opérations asynchrones sur les flux PassThrough, les réponses publiées ici ne fonctionneront pas. Une solution qui fonctionne pour les opérations asynchrones inclut la mise en mémoire tampon du contenu du flux, puis la création de flux à partir du résultat mis en mémoire tampon.

  1. Pour mettre en mémoire tampon le résultat, vous pouvez utiliser 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. Pour créer des flux à partir du tampon, vous pouvez utiliser:

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

J'ai une solution différente pour écrire simultanément sur deux flux. Naturellement, le temps d'écrire sera l'addition des deux fois, mais je l'utilise pour répondre à une demande de téléchargement, sur laquelle je veux conserver une copie du fichier téléchargé. mon serveur (en fait, j'utilise une sauvegarde S3, je mets donc en cache localement les fichiers les plus utilisés pour éviter les transferts multiples)

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

Vous pouvez ensuite l'utiliser comme un OutputStream normal

const twoStreamsOut = new TwoOutputStreams(fileOut, responseStream)

et transmettez-le à votre méthode comme s'il s'agissait d'une réponse ou d'un fileOutputStream

0
Zied Hamdi