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

Merge pull request #516 from dsundberg/websocket_ext

Added option to specify remote WebSocket URL in web socket node.
This commit is contained in:
Dave Conway-Jones 2015-01-15 17:17:21 +00:00
commit 8b3fbae3f6
2 changed files with 182 additions and 47 deletions

View File

@ -13,13 +13,80 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<script type="text/javascript">
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
</script>
<!-- WebSocket Input Node --> <!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in"> <script type="text/x-red" data-template-name="websocket in">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label> <label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server"> <input type="text" id="node-input-server">
</div> </div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@ -29,7 +96,7 @@
<script type="text/x-red" data-help-name="websocket in"> <script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p> <p>WebSocket input node.</p>
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>. <p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
The listener can be configured to expect a properly formed JSON string, in which The socket can be configured to expect a properly formed JSON string, in which
case it will parse the JSON and send on the resulting object as the entire message.</p> case it will parse the JSON and send on the resulting object as the entire message.</p>
</script> </script>
@ -38,28 +105,39 @@
category: 'input', category: 'input',
defaults: { defaults: {
name: {value:""}, name: {value:""},
server: {type:"websocket-listener"} server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
}, },
color:"rgb(215, 215, 160)", color:"rgb(215, 215, 160)",
inputs:0, inputs:0,
outputs:1, outputs:1,
icon: "white-globe.png", icon: "white-globe.png",
label: function() {
var wsNode = RED.nodes.node(this.server);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
},
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
} },
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
}); });
</script> </script>
<!-- WebSocket out Node --> <!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out"> <script type="text/x-red" data-template-name="websocket out">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label> <label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server"> <input type="text" id="node-input-server">
</div> </div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" placeholder="Name">
@ -68,7 +146,7 @@
<script type="text/x-red" data-help-name="websocket out"> <script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p> <p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The listener <p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p> over the WebSocket.</p>
@ -84,20 +162,20 @@
category: 'output', category: 'output',
defaults: { defaults: {
name: {value:""}, name: {value:""},
server: {type:"websocket-listener", required:true} server: {type:"websocket-listener", validate: ws_validateserver},
client: {type:"websocket-client", validate: ws_validateclient}
}, },
color:"rgb(215, 215, 160)", color:"rgb(215, 215, 160)",
inputs:1, inputs:1,
outputs:0, outputs:0,
icon: "white-globe.png", icon: "white-globe.png",
align: "right", align: "right",
label: function() {
var wsNode = RED.nodes.node(this.server);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
},
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
} },
label: ws_label,
oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare
}); });
</script> </script>
@ -115,7 +193,7 @@
</select> </select>
</div> </div>
<div class="form-tips"> <div class="form-tips">
Be default, <code>payload</code> will contain the data to be sent over, or received from a websocket. 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. The listener can be configured to send or receive the entire message object as a JSON formatted string.
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p> <p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
</div> </div>
@ -157,7 +235,45 @@
$("#node-config-ws-path").html(root); $("#node-config-ws-path").html(root);
$("#node-config-ws-tip").show(); $("#node-config-ws-tip").show();
} }
//document.getElementById("node-config-wsdocpath").innerHTML= }
});
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option>
</select>
</div>
<div class="form-tips">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p>
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket.
The client can be configured to send or receive the entire message object as a JSON formatted string.
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-client',{
category: 'config',
defaults: {
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
wholemsg: {value:"false"}
},
inputs:0,
outputs:0,
label: function() {
return this.path;
} }
}); });
</script> </script>

View File

@ -31,7 +31,26 @@ module.exports = function(RED) {
node.wholemsg = (n.wholemsg === "true"); node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events node._inputNodes = []; // collection of nodes that want to receive events
node._clients = {};
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
node._clients[id] = socket;
socket.on('close',function() {
delete node._clients[id];
});
socket.on('message',function(data,flags){
node.handleEvent(id,socket,'message',data,flags);
});
socket.on('error', function(err) {
node.warn("An error occured on the ws connection: "+inspect(err));
});
}
// match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
if(node.isServer)
{
var path = RED.settings.httpNodeRoot || "/"; var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path); path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
@ -45,8 +64,6 @@ module.exports = function(RED) {
} }
} }
node._clients = {};
RED.server.addListener('newListener',storeListener); RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server // Create a WebSocket Server
@ -56,19 +73,15 @@ module.exports = function(RED) {
// Stop listening for new listener events // Stop listening for new listener events
RED.server.removeListener('newListener',storeListener); RED.server.removeListener('newListener',storeListener);
node.server.on('connection', function(socket){ node.server.on('connection', handleConnection);
var id = (1+Math.random()*4294967295).toString(16); }
node._clients[id] = socket; else
socket.on('close',function() { {
delete node._clients[id]; // Connect to remote endpoint
}); var socket = new ws(node.path);
socket.on('message',function(data,flags){ node.server = socket; // keep for closing
node.handleEvent(id,socket,'message',data,flags); handleConnection(socket);
}); }
socket.on('error', function(err) {
node.warn("An error occured on the ws connection: "+inspect(err));
});
});
node.on("close", function() { node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253 // Workaround https://github.com/einaros/ws/pull/253
@ -88,6 +101,7 @@ module.exports = function(RED) {
}); });
} }
RED.nodes.registerType("websocket-listener",WebSocketListenerNode); RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){ WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
this._inputNodes.push(handler); this._inputNodes.push(handler);
@ -116,16 +130,21 @@ module.exports = function(RED) {
WebSocketListenerNode.prototype.broadcast = function(data){ WebSocketListenerNode.prototype.broadcast = function(data){
try { try {
if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) { for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data); this.server.clients[i].send(data);
} }
} }
else {
this.server.send(data);
}
}
catch(e) { // swallow any errors catch(e) { // swallow any errors
this.warn("ws:"+i+" : "+e); this.warn("ws:"+i+" : "+e);
} }
} }
WebSocketListenerNode.prototype.send = function(id,data) { WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id]; var session = this._clients[id];
if (session) { if (session) {
try { try {
@ -138,7 +157,7 @@ module.exports = function(RED) {
function WebSocketInNode(n) { function WebSocketInNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.server = n.server; this.server = (n.client)?n.client:n.server;
var node = this; var node = this;
this.serverConfig = RED.nodes.getNode(this.server); this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) { if (this.serverConfig) {
@ -152,7 +171,7 @@ module.exports = function(RED) {
function WebSocketOutNode(n) { function WebSocketOutNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
var node = this; var node = this;
this.server = n.server; this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server); this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) { if (!this.serverConfig) {
this.error("Missing server configuration"); this.error("Missing server configuration");
@ -171,7 +190,7 @@ module.exports = function(RED) {
} }
} }
if (msg._session && msg._session.type == "websocket") { if (msg._session && msg._session.type == "websocket") {
node.serverConfig.send(msg._session.id,payload); node.serverConfig.reply(msg._session.id,payload);
} else { } else {
node.serverConfig.broadcast(payload,function(error){ node.serverConfig.broadcast(payload,function(error){
if (!!error) { if (!!error) {