server.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. #!/usr/bin/env python
  2. # Based on http/server.py from Python
  3. from argparse import ArgumentParser
  4. import contextlib
  5. from http.server import SimpleHTTPRequestHandler
  6. from http.server import ThreadingHTTPServer
  7. import os
  8. import socket
  9. class MyHTTPRequestHandler(SimpleHTTPRequestHandler):
  10. extensions_map = {
  11. ".manifest": "text/cache-manifest",
  12. ".html": "text/html",
  13. ".cmake": "text/cmake",
  14. ".pc": "text/pkg-config",
  15. ".png": "image/png",
  16. ".jpg": "image/jpg",
  17. ".svg": "image/svg+xml",
  18. ".css": "text/css",
  19. ".js": "application/x-javascript",
  20. ".map": "application/json",
  21. ".wasm": "application/wasm",
  22. "": "application/octet-stream",
  23. }
  24. def __init__(self, *args, maps=None, **kwargs):
  25. self.maps = maps or []
  26. SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
  27. def end_headers(self):
  28. self.send_my_headers()
  29. SimpleHTTPRequestHandler.end_headers(self)
  30. def send_my_headers(self):
  31. self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
  32. self.send_header("Pragma", "no-cache")
  33. self.send_header("Expires", "0")
  34. def translate_path(self, path):
  35. for map_path, map_prefix in self.maps:
  36. if path.startswith(map_prefix):
  37. res = os.path.join(map_path, path.removeprefix(map_prefix).lstrip("/"))
  38. break
  39. else:
  40. res = super().translate_path(path)
  41. return res
  42. def serve_forever(port: int, ServerClass):
  43. handler = MyHTTPRequestHandler
  44. addr = ("0.0.0.0", port)
  45. with ServerClass(addr, handler) as httpd:
  46. host, port = httpd.socket.getsockname()[:2]
  47. url_host = f"[{host}]" if ":" in host else host
  48. print(f"Serving HTTP on {host} port {port} (http://{url_host}:{port}/) ...")
  49. try:
  50. httpd.serve_forever()
  51. except KeyboardInterrupt:
  52. print("\nKeyboard interrupt received, exiting.")
  53. return 0
  54. def main():
  55. parser = ArgumentParser(allow_abbrev=False)
  56. parser.add_argument("port", nargs="?", type=int, default=8080)
  57. parser.add_argument("-d", dest="directory", type=str, default=None)
  58. parser.add_argument("--map", dest="maps", nargs="+", default=[], type=str, help="Mappings, used as e.g. \"$HOME/projects/SDL:/sdl\"")
  59. args = parser.parse_args()
  60. maps = []
  61. for m in args.maps:
  62. try:
  63. path, uri = m.split(":", 1)
  64. except ValueError:
  65. parser.error(f"Invalid mapping: \"{m}\"")
  66. maps.append((path, uri))
  67. class DualStackServer(ThreadingHTTPServer):
  68. def server_bind(self):
  69. # suppress exception when protocol is IPv4
  70. with contextlib.suppress(Exception):
  71. self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
  72. return super().server_bind()
  73. def finish_request(self, request, client_address):
  74. self.RequestHandlerClass(
  75. request,
  76. client_address,
  77. self,
  78. directory=args.directory,
  79. maps=maps,
  80. )
  81. return serve_forever(
  82. port=args.port,
  83. ServerClass=DualStackServer,
  84. )
  85. if __name__ == "__main__":
  86. raise SystemExit(main())