86 votes

Node.js : Comment lire un flux dans un tampon ?

J'ai écrit une fonction assez simple qui télécharge une image à partir d'une URL donnée, la redimensionne et l'upload sur S3 (en utilisant 'gm' et 'knox'), je n'ai aucune idée si je fais la lecture d'un stream vers un buffer correctement. (tout fonctionne, mais est-ce que c'est la bonne façon de faire ?)

De plus, j'aimerais comprendre quelque chose à propos de la boucle événementielle, comment puis-je savoir qu'une invocation de la fonction ne fuira rien ou ne changera pas la variable 'buf' pour une autre invocation déjà en cours (ou bien ce scénario est impossible parce que les callbacks sont des fonctions anonymes ?)

var http = require('http');
var https = require('https');
var s3 = require('./s3');
var gm = require('gm');

module.exports.processImageUrl = function(imageUrl, filename, callback) {
var client = http;
if (imageUrl.substr(0, 5) == 'https') { client = https; }

client.get(imageUrl, function(res) {
    if (res.statusCode != 200) {
        return callback(new Error('HTTP Response code ' + res.statusCode));
    }

    gm(res)
        .geometry(1024, 768, '>')
        .stream('jpg', function(err, stdout, stderr) {
            if (!err) {
                var buf = new Buffer(0);
                stdout.on('data', function(d) {
                    buf = Buffer.concat([buf, d]);
                });

                stdout.on('end', function() {
                    var headers = {
                        'Content-Length': buf.length
                        , 'Content-Type': 'Image/jpeg'
                        , 'x-amz-acl': 'public-read'
                    };

                    s3.putBuffer(buf, '/img/d/' + filename + '.jpg', headers, function(err, res) {
                        if(err) {
                            return callback(err);
                        } else {
                            return callback(null, res.client._httpMessage.url);
                        }
                    });
                });
            } else {
                callback(err);
            }
        });
    }).on('error', function(err) {
        callback(err);
    });
};

97voto

loganfsmyth Points 25483

Dans l'ensemble, je ne vois rien qui puisse être cassé dans votre code.

Deux suggestions :

La façon dont vous combinez Buffer est sous-optimal car il doit copier toutes les données préexistantes à chaque événement "données". Il vaudrait mieux placer les morceaux dans un tableau et concat tous à la fin.

var bufs = [];
stdout.on('data', function(d){ bufs.push(d); });
stdout.on('end', function(){
  var buf = Buffer.concat(bufs);
})

En ce qui concerne les performances, je vérifierais si la bibliothèque S3 que vous utilisez prend en charge les flux. Dans l'idéal, vous n'auriez pas besoin de créer un grand tampon, mais simplement de passer le paramètre stdout directement à la bibliothèque S3.

Pour ce qui est de la deuxième partie de votre question, ce n'est pas possible. Lorsqu'une fonction est appelée, elle se voit attribuer son propre contexte privé, et tout ce qui est défini à l'intérieur de ce contexte ne sera accessible qu'à partir d'autres éléments définis à l'intérieur de cette fonction.

Mise à jour

Le vidage du fichier dans le système de fichiers permettrait probablement de réduire l'utilisation de la mémoire par requête, mais les entrées-sorties de fichiers peuvent être assez lentes, de sorte que cela n'en vaut peut-être pas la peine. Je dirais que vous ne devriez pas trop optimiser jusqu'à ce que vous puissiez profiler et tester cette fonction. Si le ramasse-miettes fait son travail, il se peut que vous soyez en train de sur-optimiser.

Cela dit, il existe de toute façon de meilleurs moyens, alors n'utilisez pas de fichiers. Puisque tout ce que vous voulez est la longueur, vous pouvez la calculer sans avoir besoin d'ajouter tous les tampons ensemble, donc vous n'avez pas besoin d'allouer un nouveau tampon.

var pause_stream = require('pause-stream');

// Your other code.

var bufs = [];
stdout.on('data', function(d){ bufs.push(d); });
stdout.on('end', function(){
  var contentLength = bufs.reduce(function(sum, buf){
    return sum + buf.length;
  }, 0);

  // Create a stream that will emit your chunks when resumed.
  var stream = pause_stream();
  stream.pause();
  while (bufs.length) stream.write(bufs.shift());
  stream.end();

  var headers = {
      'Content-Length': contentLength,
      // ...
  };

  s3.putStream(stream, ....);

31voto

bsorrentino Points 106

Extrait Javascript

function stream2buffer(stream) {

    return new Promise((resolve, reject) => {

        const _buf = [];

        stream.on("data", (chunk) => _buf.push(chunk));
        stream.on("end", () => resolve(Buffer.concat(_buf)));
        stream.on("error", (err) => reject(err));

    });
} 

Extrait de script

async function stream2buffer(stream: Stream): Promise<Buffer> {

    return new Promise < Buffer > ((resolve, reject) => {

        const _buf = Array < any > ();

        stream.on("data", chunk => _buf.push(chunk));
        stream.on("end", () => resolve(Buffer.concat(_buf)));
        stream.on("error", err => reject(`error converting stream - ${err}`));

    });
}

9voto

Vous pouvez facilement le faire en utilisant recherche de nœuds si vous utilisez des URI http(s).

Extrait du fichier readme :

fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
    .then(res => res.buffer())
    .then(buffer => console.log)

5voto

Maddocks Points 71

Je suggère la méthode loganfsmyths, qui utilise un tableau pour contenir les données.

var bufs = [];
stdout.on('data', function(d){ bufs.push(d); });
stdout.on('end', function(){
  var buf = Buffer.concat(bufs);
}

Dans mon exemple actuel, je travaille avec GRIDfs et Jimp de npm.

   var bucket = new GridFSBucket(getDBReference(), { bucketName: 'images' } );
    var dwnldStream = bucket.openDownloadStream(info[0]._id);// original size
  dwnldStream.on('data', function(chunk) {
       data.push(chunk);
    });
  dwnldStream.on('end', function() {
    var buff =Buffer.concat(data);
    console.log("buffer: ", buff);
       jimp.read(buff)
.then(image => {
         console.log("read the image!");
         IMAGE_SIZES.forEach( (size)=>{
         resize(image,size);
         });
});

J'ai fait d'autres recherches

avec une méthode string mais cela n'a pas fonctionné, peut-être parce que je lisais un fichier image, mais la méthode array a fonctionné.

const DISCLAIMER = "DONT DO THIS";
var data = "";
stdout.on('data', function(d){ 
           bufs+=d; 
         });
stdout.on('end', function(){
          var buf = Buffer.from(bufs);
          //// do work with the buffer here

          });

Lorsque j'ai utilisé la méthode string, j'ai obtenu cette erreur de npm jimp

buffer:  <Buffer 00 00 00 00 00>
{ Error: Could not find MIME for Buffer <null>

En fait, je pense que la conversion du type binaire en type chaîne de caractères n'a pas très bien fonctionné.

4voto

Vous pouvez convertir votre flux lisible en tampon et l'intégrer dans votre code de manière asynchrone comme suit.

async streamToBuffer (stream) {
    return new Promise((resolve, reject) => {
      const data = [];

      stream.on('data', (chunk) => {
        data.push(chunk);
      });

      stream.on('end', () => {
        resolve(Buffer.concat(data))
      })

      stream.on('error', (err) => {
        reject(err)
      })

    })
  }

l'utilisation serait aussi simple que :

 // usage
  const myStream // your stream
  const buffer = await streamToBuffer(myStream) // this is a buffer

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X