| #!/usr/bin/env python |
| |
| # Based on http/server.py from Python |
| |
| from argparse import ArgumentParser |
| import contextlib |
| from http.server import SimpleHTTPRequestHandler |
| from http.server import ThreadingHTTPServer |
| import os |
| import socket |
| |
| |
| class MyHTTPRequestHandler(SimpleHTTPRequestHandler): |
| extensions_map = { |
| ".manifest": "text/cache-manifest", |
| ".html": "text/html", |
| ".png": "image/png", |
| ".jpg": "image/jpg", |
| ".svg": "image/svg+xml", |
| ".css": "text/css", |
| ".js": "application/x-javascript", |
| ".wasm": "application/wasm", |
| "": "application/octet-stream", |
| } |
| |
| def __init__(self, *args, maps=None, **kwargs): |
| self.maps = maps or [] |
| SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) |
| |
| def end_headers(self): |
| self.send_my_headers() |
| SimpleHTTPRequestHandler.end_headers(self) |
| |
| def send_my_headers(self): |
| self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") |
| self.send_header("Pragma", "no-cache") |
| self.send_header("Expires", "0") |
| |
| def translate_path(self, path): |
| for map_path, map_prefix in self.maps: |
| if path.startswith(map_prefix): |
| res = os.path.join(map_path, path.removeprefix(map_prefix).lstrip("/")) |
| break |
| else: |
| res = super().translate_path(path) |
| return res |
| |
| |
| def serve_forever(port: int, ServerClass): |
| handler = MyHTTPRequestHandler |
| |
| addr = ("0.0.0.0", port) |
| with ServerClass(addr, handler) as httpd: |
| host, port = httpd.socket.getsockname()[:2] |
| url_host = f"[{host}]" if ":" in host else host |
| print(f"Serving HTTP on {host} port {port} (http://{url_host}:{port}/) ...") |
| try: |
| httpd.serve_forever() |
| except KeyboardInterrupt: |
| print("\nKeyboard interrupt received, exiting.") |
| return 0 |
| |
| |
| def main(): |
| parser = ArgumentParser(allow_abbrev=False) |
| parser.add_argument("port", nargs="?", type=int, default=8080) |
| parser.add_argument("-d", dest="directory", type=str, default=None) |
| parser.add_argument("--map", dest="maps", nargs="+", type=str, help="Mappings, used as e.g. \"$HOME/projects/SDL:/sdl\"") |
| args = parser.parse_args() |
| |
| maps = [] |
| for m in args.maps: |
| try: |
| path, uri = m.split(":", 1) |
| except ValueError: |
| parser.error(f"Invalid mapping: \"{m}\"") |
| maps.append((path, uri)) |
| |
| class DualStackServer(ThreadingHTTPServer): |
| def server_bind(self): |
| # suppress exception when protocol is IPv4 |
| with contextlib.suppress(Exception): |
| self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) |
| return super().server_bind() |
| |
| def finish_request(self, request, client_address): |
| self.RequestHandlerClass( |
| request, |
| client_address, |
| self, |
| directory=args.directory, |
| maps=maps, |
| ) |
| |
| return serve_forever( |
| port=args.port, |
| ServerClass=DualStackServer, |
| ) |
| |
| |
| if __name__ == "__main__": |
| raise SystemExit(main()) |