[英]Node.js: How to read a stream into a buffer?
我写了一个非常简单的 function 从给定的 URL 下载图像,调整它的大小并上传到 S3(使用'gm'和'knox'),我不知道我是否正在正确地将 stream 读取到缓冲区. (一切正常,但这是正确的方法吗?)
另外,我想了解一些关于事件循环的事情,我怎么知道 function 的一次调用不会泄漏任何东西或将“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);
});
};
总的来说,我没有看到任何会破坏你的代码的东西。
两个建议:
您组合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, ....);
如果您从 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)
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}`));
});
}
我建议使用 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
});
当我使用 string 方法时,我从 npm jimp 得到了这个错误
buffer: <Buffer 00 00 00 00 00>
{ Error: Could not find MIME for Buffer <null>
基本上我认为从二进制到字符串的类型转换效果不佳。
我建议有一个缓冲区数组,并在最后只连接到结果缓冲区一次。 它很容易手动完成,或者可以使用节点缓冲区
我只想发布我的解决方案。 以前的答案对我的研究非常有帮助。 我使用长度流来获取流的大小,但这里的问题是回调在流的末尾附近被触发,所以我也使用流缓存来缓存流并在我知道后将其通过管道传输到 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);
}
在 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);
}
);
}
您可以将可读流转换为缓冲区,并以这种异步方式将其集成到您的代码中。
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
注意:这仅回答“如何将流读入缓冲区?” 并忽略原始问题的上下文。
从 Node 11.14.0 开始,可读流支持异步迭代器。
const buffers = [];
// node.js readable streams implement the async iterator protocol
for await (const data of readableStream) {
buffers.push(data);
}
const finalBuffer = Buffer.concat(buffers);
奖励:在未来,通过第 2 阶段Array.fromAsync
提案,这可能会变得更好。
// 🛑 DOES NOT WORK (yet!)
const finalBuffer = Buffer.concat(await Array.fromAsync(readableStream));
您可以通过以下方式做到这一点:
async function toBuffer(stream: ReadableStream<Uint8Array>) {
const list = []
const reader = stream.getReader()
while (true) {
const { value, done } = await reader.read()
if (value)
list.push(value)
if (done)
break
}
return Buffer.concat(list)
}
或使用缓冲区消费者
const buf = buffer(stream)
您可以在 res.headers 检查“内容长度”标头。 它将为您提供您将接收的内容的长度(它将发送多少字节的数据)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.