"""Serve output/web/ on http://localhost:8000/ with HTTP Range support, which PMTiles needs. Python's built-in `python -m http.server` ignores Range headers and always returns the whole file, so the pmtiles JavaScript library cannot read a tile directory from it. This adds byte-range responses (206). Usage: python serve.py [port] """ import os import re import sys from functools import partial from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path WEB = Path(__file__).resolve().parent / "output" / "web" class RangeHandler(SimpleHTTPRequestHandler): extensions_map = {**SimpleHTTPRequestHandler.extensions_map, ".pmtiles": "application/octet-stream"} def send_head(self): match = re.fullmatch(r"bytes=(\d*)-(\d*)", self.headers.get("Range", "")) path = self.translate_path(self.path) if not match or not os.path.isfile(path): return super().send_head() size = os.path.getsize(path) start, end = match.groups() if start == "": # "bytes=-500": the last 500 bytes start, end = max(size - int(end), 0), size - 1 else: start, end = int(start), min(int(end) if end else size - 1, size - 1) if start >= size: self.send_error(416, "Requested range not satisfiable") return None f = open(path, "rb") f.seek(start) self.send_response(206) self.send_header("Content-Type", self.guess_type(path)) self.send_header("Content-Range", f"bytes {start}-{end}/{size}") self.send_header("Content-Length", str(end - start + 1)) self.send_header("Accept-Ranges", "bytes") self.end_headers() self._remaining = end - start + 1 return f def copyfile(self, source, outputfile): remaining = getattr(self, "_remaining", None) if remaining is None: return super().copyfile(source, outputfile) while remaining > 0 and (chunk := source.read(min(65536, remaining))): outputfile.write(chunk) remaining -= len(chunk) self._remaining = None if __name__ == "__main__": port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000 server = ThreadingHTTPServer(("127.0.0.1", port), partial(RangeHandler, directory=str(WEB))) print(f"serving {WEB} on http://localhost:{port}/ (Ctrl+C to stop)") server.serve_forever()