Node.js:如何将流读入缓冲区?

时间:2013-01-10 23:34:48

标签: node.js

我写了一个非常简单的函数,从给定的URL下载图像,调整大小并上传到S3(使用'gm'和'knox'),我不知道我是否正在读取流到正确缓冲。 (一切正常,但这是正确的方法吗?)

另外,我想了解一下事件循环,我怎么知道函数的一次调用不会泄漏任何东西或者将'buf'变量更改为另一个已经运行的调用(或者这种情况是不可能的,因为回调是匿名函数吗?)

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

10 个答案:

答案 0 :(得分:69)

总的来说,我没有看到任何会破坏你代码的内容。

两个建议:

组合Buffer对象的方式不够理想,因为它必须复制每个“数据”事件的所有预先存在的数据。最好将这些块放在一个数组中,最后将它们concat放在一起。

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

为了提高性能,我会调查您使用的S3库是否支持流。理想情况下,您根本不需要创建一个大缓冲区,而只需将stdout流直接传递给S3库。

关于问题的第二部分,这是不可能的。当一个函数被调用时,它被分配了自己的私有上下文,其中定义的所有内容只能从该函数内定义的其他项中访问。

更新

将文件转储到文件系统可能意味着每个请求的内存使用量更少,但文件IO可能非常慢,因此可能不值得。我要说你不应该优化太多,直到你能分析和压力测试这个功能。如果垃圾收集器正在完成它的工作,你可能会过度优化。

尽管如此,还是有更好的方法,所以不要使用文件。由于您只需要长度,因此无需将所有缓冲区附加在一起即可计算出来,因此您根本不需要分配新缓冲区。

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, ....);

答案 1 :(得分:7)

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

    });
} 

打字稿片段

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

    });
} 

答案 2 :(得分:4)

相关项目是node-stream-buffer。描述:&#34;可读和可写的流使用后备缓冲区&#34;。

答案 3 :(得分:4)

如果从http(s)URI中提取,可以使用node-fetch轻松完成此操作。

自述文件:

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

答案 4 :(得分:1)

我建议在最后只有一次缓冲区和concat到结果缓冲区。它很容易手动完成,或者可以使用node-buffers

答案 5 :(得分:1)

我只想发布我的解决方案。以前的答案对我的研究很有帮助。我使用length-stream来获取流的大小,但问题是回调是在流的末尾附近触发的,所以我也使用stream-cache来缓存流并将其传递给res对象一旦我知道内容长度。如果出现错误,

var StreamCache = require('stream-cache');
var lengthStream = require('length-stream');

var _streamFile = function(res , stream , cb){
    var cache = new StreamCache();

    var lstream = lengthStream(function(length) {
        res.header("Content-Length", length);
        cache.pipe(res);
    });

    stream.on('error', function(err){
        return cb(err);
    });

    stream.on('end', function(){
        return cb(null , true);
    });

    return stream.pipe(lstream).pipe(cache);
}

答案 6 :(得分:1)

在ts中,[]。push(bufferPart)不兼容;

所以:

getBufferFromStream(stream: Part | null): Promise<Buffer> {
    if (!stream) {
        throw 'FILE_STREAM_EMPTY';
    }
    return new Promise(
        (r, j) => {
            let buffer = Buffer.from([]);
            stream.on('data', buf => {
               buffer = Buffer.concat([buffer, buf]);
            });
            stream.on('end', () => r(buffer));
            stream.on('error', j);
        }
    );
}

答案 7 :(得分:1)

您可以将可读流转换为缓冲区,并以这种异步方式将其集成到您的代码中。

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

用法很简单:

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

答案 8 :(得分:0)

我建议使用数组来保存数据的loganfsmyths方法。

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

在我当前的工作示例中,我正在使用GRIDfs和npm的Jimp。

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

我做了其他研究

使用字符串方法,但这可能不起作用,可能是因为我正在读取图像文件,但是数组方法确实起作用。

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

          });

当我执行字符串方法时,我从npm jimp中得到了这个错误

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

基本上,我认为从二进制到字符串的类型强制转换效果不佳。

答案 9 :(得分:0)

您可以在 res.headers 中检查 "content-length" 标头。它会给你你将收到的内容的长度(它将发送多少字节的数据)