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
|
#!/usr/bin/python3
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ForkingMixIn
import io
import json
import multiprocessing
import sys
import time
# Execute [code] and evaluate [expr]. Input is given by the string [stdin].
# Return result of evaluation, the contents of stdout and stderr, and the
# exception traceback.
def run_exec(conn, code, expr=None, stdin=''):
result, out, err, exc = None, None, None, None
sys.stdin = io.StringIO(stdin)
sys.stdout = io.StringIO()
sys.stderr = io.StringIO()
try:
env = {}
if code:
exec(code, env)
if expr:
result = eval(expr, env)
except Exception as ex:
# Exception is not JSON serializable, so return traceback as string.
import traceback
exc = traceback.format_exc()
finally:
out = sys.stdout.getvalue()
err = sys.stderr.getvalue()
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
conn.send((result, out, err, exc))
# Call run_exec in a separate process for each input and return a list of
# results from those calls. If a call times out, 'timed out' is returned in
# place of exception traceback.
def run(code, inputs, timeout):
# Launch processes.
futures = []
for expr, stdin in inputs:
conn_parent, conn_child = multiprocessing.Pipe()
p = multiprocessing.Process(target=run_exec, args=(conn_child, code, expr, stdin))
p.start()
futures.append((p, conn_parent))
# Wait for results.
results = []
start = time.monotonic()
for p, conn in futures:
now = time.monotonic()
real_timeout = max(0, timeout - (now - start))
if conn.poll(real_timeout):
results.append(conn.recv())
else:
results.append((None, '', '', 'timed out'))
p.terminate()
return results
class RequestHandler(BaseHTTPRequestHandler):
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
def do_GET(self):
reply = {'status': 'error'}
if self.path == '/run':
length = int(self.headers.get('Content-Length', 0))
text = self.rfile.read(length)
request = json.loads(text.decode('utf-8'))
results = run(request.get('code', ''),
request.get('inputs', []),
request.get('timeout', 1.0))
reply = {'status': 'ok', 'results': results}
reply_text = json.dumps(reply).encode('utf-8')
self.send_response(200)
self.send_header('Content-Length', len(reply_text))
self.end_headers()
self.wfile.write(reply_text)
class ForkingHTTPServer(ForkingMixIn, HTTPServer):
pass
server = ForkingHTTPServer(('localhost', 3031), RequestHandler)
print('Python engine started.')
try:
server.serve_forever()
except KeyboardInterrupt:
print('Keyboard interrupt received, exiting.')
server.server_close()
|