summaryrefslogtreecommitdiffstats
path: root/testing/tools/websocketprocessbridge/websocketprocessbridge.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/tools/websocketprocessbridge/websocketprocessbridge.py')
-rw-r--r--testing/tools/websocketprocessbridge/websocketprocessbridge.py123
1 files changed, 123 insertions, 0 deletions
diff --git a/testing/tools/websocketprocessbridge/websocketprocessbridge.py b/testing/tools/websocketprocessbridge/websocketprocessbridge.py
new file mode 100644
index 0000000000..f922194466
--- /dev/null
+++ b/testing/tools/websocketprocessbridge/websocketprocessbridge.py
@@ -0,0 +1,123 @@
+# vim: set ts=4 et sw=4 tw=80
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from twisted.internet import protocol, reactor
+from twisted.internet.task import LoopingCall
+from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory
+
+import psutil
+
+import argparse
+import six
+import sys
+import os
+
+# maps a command issued via websocket to running an executable with args
+commands = {
+ "iceserver": [sys.executable, "-u", os.path.join("iceserver", "iceserver.py")]
+}
+
+
+class ProcessSide(protocol.ProcessProtocol):
+ """Handles the spawned process (I/O, process termination)"""
+
+ def __init__(self, socketSide):
+ self.socketSide = socketSide
+
+ def outReceived(self, data):
+ data = six.ensure_str(data)
+ if self.socketSide:
+ lines = data.splitlines()
+ for line in lines:
+ self.socketSide.sendMessage(line.encode("utf8"), False)
+
+ def errReceived(self, data):
+ self.outReceived(data)
+
+ def processEnded(self, reason):
+ if self.socketSide:
+ self.outReceived(reason.getTraceback())
+ self.socketSide.processGone()
+
+ def socketGone(self):
+ self.socketSide = None
+ self.transport.loseConnection()
+ self.transport.signalProcess("KILL")
+
+
+class SocketSide(WebSocketServerProtocol):
+ """
+ Handles the websocket (I/O, closed connection), and spawning the process
+ """
+
+ def __init__(self):
+ super(SocketSide, self).__init__()
+ self.processSide = None
+
+ def onConnect(self, request):
+ return None
+
+ def onOpen(self):
+ return None
+
+ def onMessage(self, payload, isBinary):
+ # We only expect a single message, which tells us what kind of process
+ # we're supposed to launch. ProcessSide pipes output to us for sending
+ # back to the websocket client.
+ if not self.processSide:
+ self.processSide = ProcessSide(self)
+ # We deliberately crash if |data| isn't on the "menu",
+ # or there is some problem spawning.
+ data = six.ensure_str(payload)
+ try:
+ reactor.spawnProcess(
+ self.processSide, commands[data][0], commands[data], env=os.environ
+ )
+ except BaseException as e:
+ print(e.str())
+ self.sendMessage(e.str())
+ self.processGone()
+
+ def onClose(self, wasClean, code, reason):
+ if self.processSide:
+ self.processSide.socketGone()
+
+ def processGone(self):
+ self.processSide = None
+ self.transport.loseConnection()
+
+
+# Parent process could have already exited, so this is slightly racy. Only
+# alternative is to set up a pipe between parent and child, but that requires
+# special cooperation from the parent.
+parent_process = psutil.Process(os.getpid()).parent()
+
+
+def check_parent():
+ """Checks if parent process is still alive, and exits if not"""
+ if not parent_process.is_running():
+ print("websocket/process bridge exiting because parent process is gone")
+ reactor.stop()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Starts websocket/process bridge.")
+ parser.add_argument(
+ "--port",
+ type=str,
+ dest="port",
+ default="8191",
+ help="Port for websocket/process bridge. Default 8191.",
+ )
+ args = parser.parse_args()
+
+ parent_checker = LoopingCall(check_parent)
+ parent_checker.start(1)
+
+ bridgeFactory = WebSocketServerFactory()
+ bridgeFactory.protocol = SocketSide
+ reactor.listenTCP(int(args.port), bridgeFactory)
+ print("websocket/process bridge listening on port %s" % args.port)
+ reactor.run()