我正在使用ExpressJS构建NodeJS服务器,该服务器处理通过 POST请求从桌面应用发送的数据( 50KB 至> 100MB )进行处理并退回。桌面应用gzip压缩了发送之前的数据(50KB变为4KB)。
我希望服务器解压缩数据,从数据中提取值(字符串,整数,字符,数组,json等),处理该数据,然后以处理后的数据作为响应。
我从这里开始:
apiRoute.route("/convert").post(bodyParser.raw({limit: '100Mb'}), (req, res) =>{
let outputData;
//extract values from req.body Buffer and do math on them.
//save processed data in outputData
res.json({
status: true,
data: outputData
});
});
之所以起作用,是因为主体解析器将数据解压缩到存储在内存中的缓冲区req.body
中。那是我的主要问题...内存使用率。我不想将整个数据集存储在内存中。
要解决此问题,我删除了body解析器,而是将请求流直接传递到zlib转换流中:
apiRoute.route("/convert").post((req, res) =>{
req.pipe(zlib.createGunzip());
});
现在的问题是我不知道如何从流中提取二进制值。
这就是我想要做的:
apiRoute.route("/convert").post((req, res) =>{
let binaryStream = new stream.Transform();
req
.pipe(zlib.createGunzip())
.pipe(binaryStream);
let aValue = binaryStream.getBytes(20);//returns 20 bytes
let bValue = binaryStream.getBytes(20000);//returns the next 20000 bytes
//etc...
});
但是我不知道有什么方法可以做到这一点。像Dissolve之类的模块已经关闭,但是它们要求您提前设置解析逻辑,并且所有所获取的值都存储在内存中。
另外,我不知道如何在不将所有数据都加载到内存的情况下如何响应outputData。
所以我的问题是,我怎么...
答案 0 :(得分:2)
我解决了自己的问题。我不是100%确信这是实现此目标的最佳方法,所以我愿意提出建议。
我制作了stream.Transform
的子类并实现了_transform
方法。我发现只有在调用_transform
回调时才能输入下一个数据块。知道这一点后,我将该回调函数存储为属性,并且仅在需要下一个块时才调用它。
getBytes(size)
是一种方法,该方法将从当前块中获取指定数量的字节(也保存为属性),并在需要下一个块时调用较早保存的回调。递归地执行此操作是为了解决不同大小的块和不同数量的请求字节。
然后,通过混合使用async / await和promises,我能够使整个过程保持异步(afaik)和反压。
const {Transform} = require('stream'),
events = require('events');
class ByteStream extends Transform{
constructor(options){
super(options);
this.event_emitter = new events.EventEmitter();
this.hasStarted = false;
this.hasEnded = false;
this.currentChunk;
this.nextCallback;
this.pos = 0;
this.on('finish', ()=>{
this.hasEnded = true;
this.event_emitter.emit('chunkGrabbed');
});
}
_transform(chunk, enc, callback){
this.pos = 0;
this.currentChunk = chunk;
this.nextCallback = callback;
if(!this.hasStarted){
this.hasStarted = true;
this.event_emitter.emit('started');
}
else{
this.event_emitter.emit('chunkGrabbed');
}
}
doNextCallback(){
return new Promise((resolve, reject) =>{
this.event_emitter.once('chunkGrabbed', ()=>{resolve();});
this.nextCallback();
});
}
async getBytes(size){
if(this.pos + size > this.currentChunk.length)
{
let bytes = this.currentChunk.slice(this.pos, this.currentChunk.length);
if(!this.hasEnded)
{
var newSize = size-(this.currentChunk.length - this.pos);
//grab next chunk
await this.doNextCallback();
if(!this.hasEnded){
this.pos = 0;
let recurseBytes; await this.getBytes(newSize).then(bytes => {recurseBytes = bytes;});
bytes = Buffer.concat([bytes, recurseBytes]);
}
}
return bytes;
}
else{
let bytes = this.currentChunk.slice(this.pos, this.pos+size);
this.pos += size;
return bytes;
}
}
}
module.exports = {
ByteStream : ByteStream
}
我的快递路线现在是:
apiRoute.route("/convert").post((req, res)=>{
let bStream = new ByteStream({});
let gStream = zlib.createGunzip();
bStream event_emitter.on('started', async () => {
console.log("started!");
let myValue; await bStream.getBytes(60000).then(bytes => {myValue = bytes});
console.log(myValue.length);
});
req
.pipe(gStream)
.pipe(bStream);
});
通过检查事件started
,我可以知道第一个块何时流式传输到bStream
中。从那里开始,只需使用所需的字节数调用getBytes()
,然后将承诺值分配给变量即可。尽管我还没有进行任何严格的测试,但它确实满足了我的需要。