diff options
Diffstat (limited to 'server.py')
-rwxr-xr-x | server.py | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/server.py b/server.py new file mode 100755 index 0000000..9fa7530 --- /dev/null +++ b/server.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2022 Egor Tensin <Egor.Tensin@gmail.com> +# This file is part of the "void" project. +# For details, see https://github.com/egor-tensin/void. +# Distributed under the MIT License. + +import argparse +from contextlib import contextmanager +import http.server +import os +import signal +import sys +from threading import Condition, Lock, Thread +import traceback + +from app import Request, Response, Void + + +DEFAULT_PORT = 23666 +EXITING = False + + +def script_dir(): + return os.path.dirname(os.path.realpath(__file__)) + + +def set_exiting(): + global EXITING + with SignalHandler.LOCK: + EXITING = True + SignalHandler.CV.notify() + + +class SignalHandler: + LOCK = Lock() + CV = Condition(LOCK) + + def __init__(self, httpd): + self.httpd = httpd + self.thread = Thread(target=self.run) + + def __enter__(self): + self.thread.start() + return self + + def __exit__(self, type, value, traceback): + self.thread.join() + + def run(self): + with SignalHandler.CV: + while not EXITING: + SignalHandler.CV.wait() + self.httpd.shutdown() + + +def handle_sigterm(signum, frame): + print('\nSIGTERM received, exiting...') + set_exiting() + + +@contextmanager +def setup_signal_handlers(httpd): + handler_thread = SignalHandler(httpd) + with handler_thread: + signal.signal(signal.SIGTERM, handle_sigterm) + yield + # TODO: cleanup signal handlers? + + +class RequestHandler(http.server.SimpleHTTPRequestHandler): + VOID = None + + def do_GET(self): + try: + request = Request.from_http_path(self.path) + except ValueError: + return super().do_GET() + try: + response = request.process(RequestHandler.VOID) + response.write_to_request_handler(self) + except: + status = http.server.HTTPStatus.INTERNAL_SERVER_ERROR + response = Response(traceback.format_exc(), status) + response.write_to_request_handler(self) + return + + +def make_server(port): + addr = ('', port) + server = http.server.HTTPServer + if sys.version_info >= (3, 7): + server = http.server.ThreadingHTTPServer + return server(addr, RequestHandler) + + +def parse_args(args=None): + if args is None: + args = sys.argv[1:] + parser = argparse.ArgumentParser() + parser.add_argument('-p', '--port', metavar='PORT', + type=int, default=DEFAULT_PORT, + help='set port number') + parser.add_argument('-v', '--void', metavar='PATH', dest='backup', + help='set path to the void') + return parser.parse_args(args) + + +def main(args=None): + # It's a failsafe; this script is only allowed to serve the directory it + # resides in. + os.chdir(script_dir()) + + args = parse_args(args) + with Void(args.backup) as void: + RequestHandler.VOID = void + httpd = make_server(args.port) + with setup_signal_handlers(httpd): + try: + httpd.serve_forever() + except KeyboardInterrupt: + print('\nKeyboard interrupt received, exiting...') + set_exiting() + + +if __name__ == '__main__': + main() |