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
17 # maps a command issued via websocket to running an executable with args
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
)
32 lines
= data
.splitlines()
34 self
.socketSide
.sendMessage(line
.encode("utf8"), False)
36 def errReceived(self
, data
):
37 self
.outReceived(data
)
39 def processEnded(self
, reason
):
41 self
.outReceived(reason
.getTraceback())
42 self
.socketSide
.processGone()
45 self
.socketSide
= None
46 self
.transport
.loseConnection()
47 self
.transport
.signalProcess("KILL")
50 class SocketSide(WebSocketServerProtocol
):
52 Handles the websocket (I/O, closed connection), and spawning the process
56 super(SocketSide
, self
).__init
__()
57 self
.processSide
= None
59 def onConnect(self
, request
):
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
)
76 self
.processSide
, commands
[data
][0], commands
[data
], env
=os
.environ
78 except BaseException
as e
:
80 self
.sendMessage(e
.str())
83 def onClose(self
, wasClean
, code
, reason
):
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()
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")
105 if __name__
== "__main__":
106 parser
= argparse
.ArgumentParser(description
="Starts websocket/process bridge.")
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
)