import contextlib import http.server import multiprocessing import os import pathlib import queue import shutil import socketserver import sys import tempfile @contextlib.contextmanager def spawn_web_server(dist_dir): tmp_dir = tempfile.mkdtemp() log_path = pathlib.Path(tmp_dir) / "http-server.log" q: multiprocessing.Queue[str] = multiprocessing.Queue() p = multiprocessing.Process(target=run_web_server, args=(q, log_path, dist_dir)) try: p.start() port = q.get() hostname = "127.0.0.1" print( f"Spawning webserver at http://{hostname}:{port} " f"(see logs in {log_path})" ) yield hostname, port, log_path finally: q.put("TERMINATE") p.join() shutil.rmtree(tmp_dir) def run_web_server(q, log_filepath, dist_dir): """Start the HTTP web server Parameters ---------- q : Queue communication queue log_path : pathlib.Path path to the file where to store the logs """ os.chdir(dist_dir) log_fh = log_filepath.open("w", buffering=1) sys.stdout = log_fh sys.stderr = log_fh class Handler(http.server.SimpleHTTPRequestHandler): def log_message(self, format_, *args): print( "[%s] source: %s:%s - %s" % (self.log_date_time_string(), *self.client_address, format_ % args) ) def end_headers(self): # Enable Cross-Origin Resource Sharing (CORS) self.send_header("Access-Control-Allow-Origin", "*") super().end_headers() with socketserver.TCPServer(("", 0), Handler) as httpd: host, port = httpd.server_address print(f"Starting webserver at http://{host}:{port}") httpd.server_name = "test-server" # type: ignore[attr-defined] httpd.server_port = port # type: ignore[attr-defined] q.put(port) def service_actions(): try: if q.get(False) == "TERMINATE": print("Stopping server...") sys.exit(0) except queue.Empty: pass httpd.service_actions = service_actions # type: ignore[assignment] httpd.serve_forever()