function HTTPOut(n) {
RED.nodes.createNode(this,n);
var node = this;
this.headers = n.headers||{};
this.statusCode = n.statusCode;
// ********************************** START CHANGE ***********************************
this.streaming = n.streaming || false;
this.responses = [];
const boundary = 'myboundary';
// ********************************** END CHANGE ***********************************
this.on("input",function(msg) {
if (msg.res) {
// ********************************** START CHANGE ***********************************
if (node.streaming == true) {
node.responses.push(msg.res);
// Handle a disconnect (i.e. remove the response object from the array)
msg.res._res.connection.on('close', function() {
var index = node.responses.indexOf(msg.res);
if (index > -1) {
node.responses.splice(index, 1);
}
});
msg.res._res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace;boundary=--' + boundary,
'Connection': 'keep-alive',
'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Pragma': 'no-cache'
})
}
// ********************************** END CHANGE ***********************************
var headers = RED.util.cloneMessage(node.headers);
if (msg.headers) {
if (msg.headers.hasOwnProperty('x-node-red-request-node')) {
var headerHash = msg.headers['x-node-red-request-node'];
delete msg.headers['x-node-red-request-node'];
var hash = hashSum(msg.headers);
if (hash === headerHash) {
delete msg.headers;
}
}
if (msg.headers) {
for (var h in msg.headers) {
if (msg.headers.hasOwnProperty(h) && !headers.hasOwnProperty(h)) {
headers[h] = msg.headers[h];
}
}
}
}
if (Object.keys(headers).length > 0) {
msg.res._res.set(headers);
}
if (msg.cookies) {
for (var name in msg.cookies) {
if (msg.cookies.hasOwnProperty(name)) {
if (msg.cookies[name] === null || msg.cookies[name].value === null) {
if (msg.cookies[name]!==null) {
msg.res._res.clearCookie(name,msg.cookies[name]);
} else {
msg.res._res.clearCookie(name);
}
} else if (typeof msg.cookies[name] === 'object') {
msg.res._res.cookie(name,msg.cookies[name].value,msg.cookies[name]);
} else {
msg.res._res.cookie(name,msg.cookies[name]);
}
}
}
}
if (node.streaming == false) {
var statusCode = node.statusCode || msg.statusCode || 200;
if (typeof msg.payload == "object" && !Buffer.isBuffer(msg.payload)) {
msg.res._res.status(statusCode).jsonp(msg.payload);
} else {
if (msg.res._res.get('content-length') == null) {
var len;
if (msg.payload == null) {
len = 0;
} else if (Buffer.isBuffer(msg.payload)) {
len = msg.payload.length;
} else if (typeof msg.payload == "number") {
len = Buffer.byteLength(""+msg.payload);
} else {
len = Buffer.byteLength(msg.payload);
}
msg.res._res.set('content-length', len);
}
if (typeof msg.payload === "number") {
msg.payload = ""+msg.payload;
}
msg.res._res.status(statusCode).send(msg.payload);
}
}
} else {
// ********************************** START CHANGE ***********************************
if (node.streaming == false) {
node.warn(RED._("httpin.errors.no-response"));
}
else {
if(msg.payload && !msg.res) {
node.responses.forEach(function(resp) {
resp._res.write('--' + boundary);
resp._res.write('\r\n');
resp._res.write('Content-Type: image/jpeg');
resp._res.write('\r\n');
resp._res.write('Content-length: ' + msg.payload.length);
resp._res.write('\r\n');
resp._res.write('\r\n');
resp._res.write(msg.payload);
resp._res.write('\r\n');
resp._res.write('\r\n');
//resp._res.flush();
});
}
}
// ********************************** END CHANGE ***********************************
}
});
}
RED.nodes.registerType("http response",HTTPOut);
if(msg.payload && !msg.res) {
node.responses.forEach(function(resp) {
...
resp._res.write(msg.payload);
...
});
}
However the write does only accept string or buffer types. I'm not sure how I have to handle other types (dates, numbers, json objects ...). Would be great if somebody could give some code/advise on this matter...
--
http://nodered.org
Join us on Slack to continue the conversation: http://nodered.org/slack
---
You received this message because you are subscribed to the Google Groups "Node-RED" group.
To unsubscribe from this group and stop receiving emails from it, send an email to node-red+unsubscribe@googlegroups.com.
To post to this group, send email to node...@googlegroups.com.
Visit this group at https://groups.google.com/group/node-red.
To view this discussion on the web, visit https://groups.google.com/d/msgid/node-red/835cf815-e8c1-434d-b57a-c11f19b54653%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
The default option would be 'disabled' for existing flows. When 'client independent' is selected, all msg.payload data would be send to ALL responses/clients (available in the node's array): this way the clients can hook in, in the middle of a stream that is already running. When 'client dependent' is selected, the msg.payload will ONLY be send to the response object available in the msg.res field: this way the clients get there own private stream from the start.
When the first message (1) arrives in the httpresponse node, the headers (at stream start) are being written. When the next messages (2) arrive, the data in the msg.payloads of those messages are written to the stream. Or is this not correct, since the order of 1 and 2 can be reversed by the event queue mechanism : in that case the stream start headers would be written after the data has arrived ?
And perhaps it is not a good practice to pass the response object in all the messages (msg.res), but instead it might perhaps be better to pass a simple response identifier ? So that 1 sends a response object, while 2 passes only the response identifier (and the httpresponse node stores the responses in a map with the identifier as key) ...
Suddenly I have more questions than answers ;-(
Bart
HTTP/1.0 200 OK
Content-Type: multipart/x-mixed-replace;boundary=--myboundary
Connection: keep-alive
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
--boundary
Content-Type: image/jpeg
Content-Length: [length of the content data bytes]
[content data bytes e.g. camera image]
--boundary
Content-Type: image/jpeg
Content-Length: [length of the content data bytes]
[content data bytes e.g. camera image]
...
At the start of the stream we have a global http header. The content-type header field needs to contain the 'multipart' and 'boundary' stuff, and the other cache-related headers should also be available. If somebody should remove these, the streaming simply doesn't work. I could of course add these required headers by default to the 'headers' section on the config screen:
This way a user could change them (or add extra headers), but they are at least set by default.
Moreover each part of the multipart stream also contains a part http header, which also should be dynamically adjustable. I assume the most easy way to implement this, is to display a second list (in case streaming is enabled):
Moreover, both types of headers should be dynamically adjustable using the input message. I could a msg.partheaders field beside to the already existing msg.headers , but I think that would become confusing: indeed the global headers can only be applied once (at the start of the stream). When applied afterwards, following error would occur:
Error: Can't set headers after they are sent.
This means that in the 2nd and next messages these msg.headers field should be ignored by the node (which might be confusing: users set headers and they are not used). Therefore it might be better to reuse the existing msg.headers field both for global headers and part headers. As a result the first message would contain the global headers (and the payload will be ignored, i.e. not send), and from the 2nd message it contains the part headers (and the payload contains data that will be send).
Everybody is welcome to join this discussion !
Kind regards,
Bart
The config view now has a separate tables for global headers and part headers to be able to implement Nick's proposal (to make the headers adjustable). These tables are filled with default headers that are needed for a multipart stream;