Bug 1885602 - Part 4: Implement navigating to the settings from the menu header for...
[gecko.git] / testing / tools / websocketprocessbridge / websocketprocessbridge.py
blobf922194466f18ec12d79339776ad60f02b812c9d
1 # vim: set ts=4 et sw=4 tw=80
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 from twisted.internet import protocol, reactor
7 from twisted.internet.task import LoopingCall
8 from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory
10 import psutil
12 import argparse
13 import six
14 import sys
15 import os
17 # maps a command issued via websocket to running an executable with args
18 commands = {
19 "iceserver": [sys.executable, "-u", os.path.join("iceserver", "iceserver.py")]
23 class ProcessSide(protocol.ProcessProtocol):
24 """Handles the spawned process (I/O, process termination)"""
26 def __init__(self, socketSide):
27 self.socketSide = socketSide
29 def outReceived(self, data):
30 data = six.ensure_str(data)
31 if self.socketSide:
32 lines = data.splitlines()
33 for line in lines:
34 self.socketSide.sendMessage(line.encode("utf8"), False)
36 def errReceived(self, data):
37 self.outReceived(data)
39 def processEnded(self, reason):
40 if self.socketSide:
41 self.outReceived(reason.getTraceback())
42 self.socketSide.processGone()
44 def socketGone(self):
45 self.socketSide = None
46 self.transport.loseConnection()
47 self.transport.signalProcess("KILL")
50 class SocketSide(WebSocketServerProtocol):
51 """
52 Handles the websocket (I/O, closed connection), and spawning the process
53 """
55 def __init__(self):
56 super(SocketSide, self).__init__()
57 self.processSide = None
59 def onConnect(self, request):
60 return None
62 def onOpen(self):
63 return None
65 def onMessage(self, payload, isBinary):
66 # We only expect a single message, which tells us what kind of process
67 # we're supposed to launch. ProcessSide pipes output to us for sending
68 # back to the websocket client.
69 if not self.processSide:
70 self.processSide = ProcessSide(self)
71 # We deliberately crash if |data| isn't on the "menu",
72 # or there is some problem spawning.
73 data = six.ensure_str(payload)
74 try:
75 reactor.spawnProcess(
76 self.processSide, commands[data][0], commands[data], env=os.environ
78 except BaseException as e:
79 print(e.str())
80 self.sendMessage(e.str())
81 self.processGone()
83 def onClose(self, wasClean, code, reason):
84 if self.processSide:
85 self.processSide.socketGone()
87 def processGone(self):
88 self.processSide = None
89 self.transport.loseConnection()
92 # Parent process could have already exited, so this is slightly racy. Only
93 # alternative is to set up a pipe between parent and child, but that requires
94 # special cooperation from the parent.
95 parent_process = psutil.Process(os.getpid()).parent()
98 def check_parent():
99 """Checks if parent process is still alive, and exits if not"""
100 if not parent_process.is_running():
101 print("websocket/process bridge exiting because parent process is gone")
102 reactor.stop()
105 if __name__ == "__main__":
106 parser = argparse.ArgumentParser(description="Starts websocket/process bridge.")
107 parser.add_argument(
108 "--port",
109 type=str,
110 dest="port",
111 default="8191",
112 help="Port for websocket/process bridge. Default 8191.",
114 args = parser.parse_args()
116 parent_checker = LoopingCall(check_parent)
117 parent_checker.start(1)
119 bridgeFactory = WebSocketServerFactory()
120 bridgeFactory.protocol = SocketSide
121 reactor.listenTCP(int(args.port), bridgeFactory)
122 print("websocket/process bridge listening on port %s" % args.port)
123 reactor.run()