Hi Dave,
this might be related to a similar problem I ran into. For another
discussion I have created my own
local version of the HttpRequest node, to implement HTTP multipart streaming. Currently it seems to work fine: I can do MJPEG streaming from all kind of ip camera's. The only problem is performance on my Raspberry when Node-Red needs to process the high amount of images that keeps arriving ...
Current situation
After some investigation, I found that the HttpRequest node was consuming lots of memory and CPU.
What happens: If you specify to return a binary buffer, it will set encoding to 'binary'. However 'binary' is obsolete and should be replaced by encoding 'null'. However, due to an
issue in NodeJs the 'null' will be ignored and it will keep its default stringencoder. As a result:
a HTTP response arrives in NodeJs as a binary buffer, which is converted by NodeJs to an UTF8 string, which is again afterwards converted to a binary buffer by Node-Red. This will consume a lot of CPU and memory...
I will show it here in detail for your test flow.
1) Node-Red sets the encoding to 'binary':
2) When the request has been send, the response data will arrive as string chunks (which are appended to one large string):
3) At the end, the msg.payload contains a large string (with all the data chunks). Then Node-Red will convert it back to a Buffer, since we asked it:
So in your example this will work fine: the output of the HttpRequest node is a Buffer, and the image is stored on disc.
However, a lot of useless conversions took place (which moreover might corrupt our bytes like newline characters).
My solution
Your example flow is only about a single image. However, in my endless HTTP streaming this becomes a real performance issue. Here in detail how I worked around the issue:
1) I implemented the workaround (from the NodeJs issue link) by removing the string encoder in NodeJs:
2) Next step is to initialize the msg.payload as an array (of Buffers).
3) When the request has been send, the response data will arrive as buffer chunks (which are appended to the Buffer array) since NodeJs doesn't have a stringencoder anymore:
Remark: I used Buffer.concat first, but then the entire buffer will need to be copied underneath every time a new chunk arrives. This would become again a performance killer.
4) At the end, the msg.payload contains an array (with all the data chunk buffers). We will concatenate all the buffers into one large buffer:
The end result will be the same: the output of the HttpRequest node is a Buffer, and the image is stored on disc.
But it will run much smoother.
Conclusion
Hopefully this can solve the problem of Kevin, since I also had corrupted images in my multipart stream (due to characters being removed in the string conversion). But I'm not sure ...
However, it would be great if this could be fixed anyway in Node-Red.
I have run out of spare time to create a pull-request for this, but this is the part of 21-httprequest.js that I have fixed:
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
//(node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8');
if (node.ret === "bin") {
res.setEncoding(null);
delete res._readableState.decoder;
}
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.responseUrl = res.responseUrl;
if (node.ret === "bin") {
msg.payload = [];
}
else {
msg.payload = "";
}
// msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {
if (node.ret === "bin") {
msg.payload.push(chunk);
}
else {
msg.payload += chunk;
}
});
res.on('end',function() {
if (node.metric()) {
// Calculate request time
var diff = process.hrtime(preRequestTimestamp);
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
var metricRequestDurationMillis = ms.toFixed(3);
node.metric("duration.millis", msg, metricRequestDurationMillis);
if (res.client && res.client.bytesRead) {
node.metric("size.bytes", msg, res.client.bytesRead);
}
}
if (node.ret === "bin") {
//msg.payload = new Buffer(msg.payload,"binary");
msg.payload = Buffer.concat(msg.payload);
}
else if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); }
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
}
node.send(msg);
node.status({});
});
});
Kind regards,
Bart Butenaers