Files
iconograph/server/https_server.py

142 lines
3.5 KiB
Python
Raw Normal View History

2016-04-29 16:45:58 -07:00
#!/usr/bin/python3
import argparse
2016-05-03 14:49:23 -07:00
import os
import pyinotify
2016-05-03 13:38:58 -07:00
from gevent import pywsgi
2016-04-29 16:45:58 -07:00
import ssl
import sys
import threading
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-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()
class INotifyHandler(pyinotify.ProcessEvent):
def process_IN_MOVED_TO(self, event):
if event.name != 'manifest.json':
return
image_type = os.path.basename(event.path)
print('new manifest: %r' % image_type)
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
def __init__(self, image_path):
self._image_path = image_path
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)
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('.')
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
class Server(object):
2016-04-29 16:45:58 -07:00
2016-05-03 14:49:23 -07:00
def __init__(self, listen_host, listen_port, server_key, server_cert, ca_cert, image_path):
2016-04-29 16:45:58 -07:00
wm = pyinotify.WatchManager()
inotify_handler = INotifyHandler()
self._notifier = pyinotify.Notifier(wm, inotify_handler)
wm.add_watch(image_path, pyinotify.IN_MOVED_TO, rec=True, auto_add=True)
2016-05-03 13:38:58 -07:00
http_handler = HTTPRequestHandler(image_path)
2016-05-03 13:38:58 -07:00
self._httpd = pywsgi.WSGIServer(
(listen_host, listen_port),
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):
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():
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,
FLAGS.image_path)
2016-04-29 16:45:58 -07:00
server.Serve()
if __name__ == '__main__':
main()