1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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()
|