2016-04-29 16:45:58 -07:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
|
|
import argparse
|
2016-05-03 23:52:58 +00:00
|
|
|
import json
|
2016-05-03 14:49:23 -07:00
|
|
|
import os
|
2016-05-03 23:19:39 +00:00
|
|
|
import pyinotify
|
2016-04-29 16:45:58 -07:00
|
|
|
import ssl
|
2016-05-03 23:19:39 +00:00
|
|
|
import sys
|
|
|
|
|
import threading
|
2016-05-03 23:52:58 +00:00
|
|
|
from ws4py import websocket
|
|
|
|
|
from ws4py.server import geventserver
|
|
|
|
|
from ws4py.server import wsgiutils
|
2016-04-29 16:45:58 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='iconograph https_server')
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'--ca-cert',
|
|
|
|
|
dest='ca_cert',
|
|
|
|
|
action='store',
|
|
|
|
|
required=True)
|
2016-05-03 14:49:23 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--image-path',
|
|
|
|
|
dest='image_path',
|
|
|
|
|
action='store',
|
|
|
|
|
required=True)
|
2016-05-03 23:58:48 +00:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--image-type',
|
|
|
|
|
dest='image_types',
|
|
|
|
|
action='append',
|
|
|
|
|
required=True)
|
2016-04-29 16:45:58 -07:00
|
|
|
parser.add_argument(
|
|
|
|
|
'--listen-host',
|
|
|
|
|
dest='listen_host',
|
|
|
|
|
action='store',
|
|
|
|
|
default='::')
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'--listen-port',
|
|
|
|
|
dest='listen_port',
|
|
|
|
|
type=int,
|
|
|
|
|
action='store',
|
|
|
|
|
default=443)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'--server-key',
|
|
|
|
|
dest='server_key',
|
|
|
|
|
action='store',
|
|
|
|
|
required=True)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
'--server-cert',
|
|
|
|
|
dest='server_cert',
|
|
|
|
|
action='store',
|
|
|
|
|
required=True)
|
|
|
|
|
FLAGS = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
2016-05-04 00:02:12 +00:00
|
|
|
def GetWebSocketHandler(image_types, websockets):
|
2016-05-03 23:52:58 +00:00
|
|
|
class WebSocketHandler(websocket.WebSocket):
|
|
|
|
|
def opened(self):
|
2016-05-04 00:02:12 +00:00
|
|
|
self.send(json.dumps({
|
|
|
|
|
'type': 'image_types',
|
|
|
|
|
'data': {
|
|
|
|
|
'image_types': list(image_types),
|
|
|
|
|
},
|
|
|
|
|
}), False)
|
2016-05-03 23:52:58 +00:00
|
|
|
websockets.add(self)
|
|
|
|
|
|
|
|
|
|
def closed(self, code, reason=None):
|
|
|
|
|
websockets.remove(self)
|
|
|
|
|
|
|
|
|
|
return WebSocketHandler
|
|
|
|
|
|
|
|
|
|
|
2016-05-03 23:19:39 +00:00
|
|
|
class INotifyHandler(pyinotify.ProcessEvent):
|
2016-05-03 23:52:58 +00:00
|
|
|
def __init__(self, websockets):
|
|
|
|
|
self._websockets = websockets
|
|
|
|
|
|
2016-05-03 23:19:39 +00:00
|
|
|
def process_IN_MOVED_TO(self, event):
|
|
|
|
|
if event.name != 'manifest.json':
|
|
|
|
|
return
|
|
|
|
|
image_type = os.path.basename(event.path)
|
2016-05-03 23:52:58 +00:00
|
|
|
for websocket in self._websockets:
|
|
|
|
|
websocket.send(json.dumps({
|
|
|
|
|
'type': 'new_manifest',
|
|
|
|
|
'data': {
|
|
|
|
|
'image_type': image_type,
|
|
|
|
|
},
|
|
|
|
|
}), False)
|
2016-05-03 23:19:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class HTTPRequestHandler(object):
|
2016-05-03 14:09:45 -07:00
|
|
|
|
2016-05-03 14:49:23 -07:00
|
|
|
_MIME_TYPES = {
|
|
|
|
|
'.iso': 'application/octet-stream',
|
|
|
|
|
'.json': 'application/json',
|
|
|
|
|
}
|
|
|
|
|
_BLOCK_SIZE = 2 ** 16
|
|
|
|
|
|
2016-05-03 23:58:48 +00:00
|
|
|
def __init__(self, image_path, image_types, websockets):
|
2016-05-03 14:49:23 -07:00
|
|
|
self._image_path = image_path
|
2016-05-03 23:58:48 +00:00
|
|
|
self._image_types = image_types
|
2016-05-04 00:02:12 +00:00
|
|
|
inner_handler = GetWebSocketHandler(image_types, websockets)
|
2016-05-03 23:52:58 +00:00
|
|
|
self._websocket_handler = wsgiutils.WebSocketWSGIApplication(handler_cls=inner_handler)
|
2016-05-03 14:49:23 -07:00
|
|
|
|
2016-05-03 14:09:45 -07:00
|
|
|
def __call__(self, env, start_response):
|
2016-05-03 14:49:23 -07:00
|
|
|
path = env['PATH_INFO']
|
|
|
|
|
if path.startswith('/image/'):
|
|
|
|
|
image_type, image_name = path[7:].split('/', 1)
|
|
|
|
|
return self._ServeImageFile(start_response, image_type, image_name)
|
2016-05-03 23:52:58 +00:00
|
|
|
elif path == '/ws':
|
|
|
|
|
return self._websocket_handler(env, start_response)
|
2016-05-03 14:49:23 -07:00
|
|
|
|
|
|
|
|
start_response('404 Not found', [('Content-Type', 'text/plain')])
|
|
|
|
|
return [b'Not found']
|
|
|
|
|
|
|
|
|
|
def _MIMEType(self, file_name):
|
|
|
|
|
for suffix, mime_type in self._MIME_TYPES.items():
|
|
|
|
|
if file_name.endswith(suffix):
|
|
|
|
|
return mime_type
|
|
|
|
|
|
|
|
|
|
def _ServeImageFile(self, start_response, image_type, image_name):
|
|
|
|
|
# Sanitize inputs
|
|
|
|
|
image_type = os.path.basename(image_type)
|
|
|
|
|
image_name = os.path.basename(image_name)
|
|
|
|
|
assert not image_type.startswith('.')
|
|
|
|
|
assert not image_name.startswith('.')
|
2016-05-03 23:58:48 +00:00
|
|
|
assert image_type in self._image_types
|
2016-05-03 14:49:23 -07:00
|
|
|
|
|
|
|
|
file_path = os.path.join(self._image_path, image_type, image_name)
|
|
|
|
|
try:
|
|
|
|
|
with open(file_path, 'rb') as fh:
|
|
|
|
|
start_response('200 OK', [('Content-Type', self._MIMEType(image_name))])
|
|
|
|
|
while True:
|
|
|
|
|
block = fh.read(self._BLOCK_SIZE)
|
|
|
|
|
if len(block) == 0:
|
|
|
|
|
break
|
|
|
|
|
yield block
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
start_response('404 Not found')
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
return
|
2016-05-03 14:09:45 -07:00
|
|
|
|
|
|
|
|
|
2016-05-03 23:19:39 +00:00
|
|
|
class Server(object):
|
2016-04-29 16:45:58 -07:00
|
|
|
|
2016-05-03 23:58:48 +00:00
|
|
|
def __init__(self, listen_host, listen_port, server_key, server_cert, ca_cert, image_path, image_types):
|
2016-05-03 23:52:58 +00:00
|
|
|
websockets = set()
|
2016-04-29 16:45:58 -07:00
|
|
|
|
2016-05-03 23:19:39 +00:00
|
|
|
wm = pyinotify.WatchManager()
|
2016-05-03 23:52:58 +00:00
|
|
|
inotify_handler = INotifyHandler(websockets)
|
2016-05-03 23:19:39 +00:00
|
|
|
self._notifier = pyinotify.Notifier(wm, inotify_handler)
|
2016-05-03 23:58:48 +00:00
|
|
|
for image_type in image_types:
|
|
|
|
|
type_path = os.path.join(image_path, image_type)
|
|
|
|
|
wm.add_watch(type_path, pyinotify.IN_MOVED_TO)
|
2016-05-03 13:38:58 -07:00
|
|
|
|
2016-05-03 23:58:48 +00:00
|
|
|
http_handler = HTTPRequestHandler(image_path, image_types, websockets)
|
2016-05-03 23:52:58 +00:00
|
|
|
self._httpd = geventserver.WSGIServer(
|
2016-05-03 13:38:58 -07:00
|
|
|
(listen_host, listen_port),
|
2016-05-03 23:19:39 +00:00
|
|
|
http_handler,
|
2016-04-29 16:45:58 -07:00
|
|
|
keyfile=server_key,
|
|
|
|
|
certfile=server_cert,
|
|
|
|
|
ca_certs=ca_cert,
|
|
|
|
|
cert_reqs=ssl.CERT_REQUIRED,
|
|
|
|
|
ssl_version=ssl.PROTOCOL_TLSv1_2)
|
|
|
|
|
|
|
|
|
|
def Serve(self):
|
2016-05-03 23:19:39 +00:00
|
|
|
self._notify_thread = threading.Thread(target=self._notifier.loop)
|
|
|
|
|
self._notify_thread.daemon = True
|
|
|
|
|
self._notify_thread.start()
|
2016-04-29 16:45:58 -07:00
|
|
|
self._httpd.serve_forever()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2016-05-03 23:19:39 +00:00
|
|
|
server = Server(
|
2016-04-29 16:45:58 -07:00
|
|
|
FLAGS.listen_host,
|
|
|
|
|
FLAGS.listen_port,
|
|
|
|
|
FLAGS.server_key,
|
|
|
|
|
FLAGS.server_cert,
|
2016-05-03 14:49:23 -07:00
|
|
|
FLAGS.ca_cert,
|
2016-05-03 23:58:48 +00:00
|
|
|
FLAGS.image_path,
|
|
|
|
|
set(FLAGS.image_types))
|
2016-04-29 16:45:58 -07:00
|
|
|
server.Serve()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|