1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #3056 from node-red/ws-ping

Allow websocket client node to send pings
This commit is contained in:
Nick O'Leary 2021-07-12 19:40:57 +01:00 committed by GitHub
commit ed5567fc73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 5 deletions

View File

@ -176,7 +176,8 @@
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
tls: {type:"tls-config",required: false},
wholemsg: {value:"false"}
wholemsg: {value:"false"},
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) }
},
inputs:0,
outputs:0,
@ -188,11 +189,24 @@
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
});
$("#node-config-input-path").change();
var heartbeatActive = (this.hb && this.hb != "0");
$("#node-config-input-hb-cb").prop("checked",heartbeatActive);
$("#node-config-input-hb-cb").on("change", function(evt) {
$("#node-config-input-hb-row").toggle(this.checked);
})
$("#node-config-input-hb-cb").trigger("change");
if (!heartbeatActive) {
$("#node-config-input-hb").val("");
}
},
oneditsave: function() {
if (!/^wss:/i.test($("#node-config-input-path").val())) {
$("#node-config-input-tls").val("_ADD_");
}
if (!$("#node-config-input-hb-cb").prop("checked")) {
$("#node-config-input-hb").val("0");
}
}
});
@ -259,6 +273,14 @@
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-row" style="display: flex; align-items: center; min-height: 34px">
<label for="node-config-input-hb-cb" data-i18n="websocket.sendheartbeat"></label>
<input type="checkbox" style="margin: 0 8px; width:auto" id="node-config-input-hb-cb">
<span id="node-config-input-hb-row" class="hide" >
<input type="text" style="width: 70px; margin-right: 3px" id="node-config-input-hb">
<span data-i18n="inject.seconds"></span>
</span>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>

View File

@ -55,6 +55,13 @@ module.exports = function(RED) {
node.closing = false;
node.tls = n.tls;
if (n.hb) {
var heartbeat = parseInt(n.hb);
if (heartbeat > 0) {
node.heartbeat = heartbeat * 1000;
}
}
function startconn() { // Connect to remote endpoint
node.tout = null;
var prox, noprox;
@ -93,9 +100,24 @@ module.exports = function(RED) {
function handleConnection(/*socket*/socket) {
var id = RED.util.generateId();
socket.nrId = id;
socket.nrPendingHeartbeat = false;
if (node.isServer) {
node._clients[id] = socket;
node.emit('opened',{count:Object.keys(node._clients).length,id:id});
} else {
if (node.heartbeat) {
node.heartbeatInterval = setInterval(function() {
if (socket.nrPendingHeartbeat) {
// No pong received
socket.terminate();
socket.nrErrorHandler(new Error("timeout"));
return;
}
socket.nrPendingHeartbeat = true;
socket.ping();
},node.heartbeat);
}
}
socket.on('open',function() {
if (!node.isServer) {
@ -103,6 +125,7 @@ module.exports = function(RED) {
}
});
socket.on('close',function() {
clearInterval(node.heartbeatInterval);
if (node.isServer) {
delete node._clients[id];
node.emit('closed',{count:Object.keys(node._clients).length,id:id});
@ -117,13 +140,21 @@ module.exports = function(RED) {
socket.on('message',function(data,flags) {
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
socket.nrErrorHandler = function(err) {
clearInterval(node.heartbeatInterval);
node.emit('erro',{err:err,id:id});
if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
}
});
}
socket.on('error',socket.nrErrorHandler);
socket.on('ping', function() {
socket.nrPendingHeartbeat = false;
})
socket.on('pong', function() {
socket.nrPendingHeartbeat = false;
})
}
if (node.isServer) {
@ -152,6 +183,19 @@ module.exports = function(RED) {
node.server = new ws.Server(serverOptions);
node.server.setMaxListeners(0);
node.server.on('connection', handleConnection);
// Not adding server-initiated heartbeats yet
// node.heartbeatInterval = setInterval(function() {
// node.server.clients.forEach(function(ws) {
// if (ws.nrPendingHeartbeat) {
// // No pong received
// ws.terminate();
// ws.nrErrorHandler(new Error("timeout"));
// return;
// }
// ws.nrPendingHeartbeat = true;
// ws.ping();
// });
// })
}
else {
node.closing = false;
@ -159,6 +203,9 @@ module.exports = function(RED) {
}
node.on("close", function() {
if (node.heartbeatInterval) {
clearInterval(node.heartbeatInterval);
}
if (node.isServer) {
delete listenerNodes[node.fullPath];
node.server.close();
@ -168,8 +215,6 @@ module.exports = function(RED) {
// RED.server.removeListener('upgrade', handleServerUpgrade);
// serverUpgradeAdded = false;
// }
}
else {
node.closing = true;

View File

@ -512,6 +512,7 @@
"sendrec": "Send/Receive",
"payload": "payload",
"message": "entire message",
"sendheartbeat": "Send heartbeat",
"tip": {
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
"path2": "This path will be relative to <code>__path__</code>.",