diff options
Diffstat (limited to 'testing/tools/websocketprocessbridge/websocketprocessbridge.py')
-rw-r--r-- | testing/tools/websocketprocessbridge/websocketprocessbridge.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/testing/tools/websocketprocessbridge/websocketprocessbridge.py b/testing/tools/websocketprocessbridge/websocketprocessbridge.py new file mode 100644 index 000000000..57bab31a4 --- /dev/null +++ b/testing/tools/websocketprocessbridge/websocketprocessbridge.py @@ -0,0 +1,106 @@ +# 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 +import txws +import psutil + +import argparse +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): + if self.socketSide: + lines = data.splitlines() + for line in lines: + self.socketSide.transport.write(line) + + def errReceived(self, data): + self.outReceived(data) + + def processEnded(self, reason): + if self.socketSide: + self.outReceived(str(reason)) + self.socketSide.processGone() + + def socketGone(self): + self.socketSide = None + self.transport.loseConnection() + self.transport.signalProcess("KILL") + + +class SocketSide(protocol.Protocol): + """ + Handles the websocket (I/O, closed connection), and spawning the process + """ + + def __init__(self): + self.processSide = None + + def dataReceived(self, data): + if not self.processSide: + self.processSide = ProcessSide(self) + # We deliberately crash if |data| isn't on the "menu", + # or there is some problem spawning. + reactor.spawnProcess(self.processSide, + commands[data][0], + commands[data], + env=os.environ) + + def connectionLost(self, reason): + if self.processSide: + self.processSide.socketGone() + + def processGone(self): + self.processSide = None + self.transport.loseConnection() + + +class ProcessSocketBridgeFactory(protocol.Factory): + """Builds sockets that can launch/bridge to a process""" + + def buildProtocol(self, addr): + return SocketSide() + +# 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 = ProcessSocketBridgeFactory() + reactor.listenTCP(int(args.port), txws.WebSocketFactory(bridgeFactory)) + print("websocket/process bridge listening on port %s" % args.port) + reactor.run() + + |