GODSIGH/serve.py
jing 7356d62381 phase 1: globe core — viewer, dual basemaps, clock, HUD, layer contract
- CesiumJS viewer (token-free, baseLayer:false), Carto dark + EOX Sentinel-2
- clock spans now +/-6h CLAMPED; built-in timeline/animation = the space-time slider
- HUD: brand, LIVE/SCRUBBED chip, Data/Photo mode toggle, layer rows, credits
- lighting is mode-aware: OFF in Data (high-contrast asset map), ON in Photo
  (realistic day/night terminator) — refines SPEC 6.1's global-lighting note
- main.js defines the layer-factory contract + dynamic loader; layers land next
- serve.py now forwards OpenSky X-Rate-Limit-* headers for the aircraft status line

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:06:05 +10:00

65 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""GODSIGH dev server: static files + same-origin proxy for feeds that block CORS."""
import sys
import urllib.error
import urllib.request
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
UPSTREAMS = {
# OpenSky pins Access-Control-Allow-Origin to its own domain, so the browser
# can't call it directly — we forward it here instead.
"opensky": "https://opensky-network.org/api/states/all",
# Celestrak sends ACAO:* today; proxied too so the app keeps working if that changes.
"celestrak": "https://celestrak.org/NORAD/elements/gp.php",
}
class Handler(SimpleHTTPRequestHandler):
def do_GET(self):
if not self.path.startswith("/proxy/"):
return super().do_GET()
name, _, query = self.path[len("/proxy/"):].partition("?")
base = UPSTREAMS.get(name)
if not base:
return self.send_error(404, f"unknown upstream {name!r}")
url = base + ("?" + query if query else "")
# Forward these upstream headers so the client can show quota state.
passthrough = {}
def grab(hdrs):
for k in ("X-Rate-Limit-Remaining", "X-Rate-Limit-Limit", "X-Expires-After"):
if hdrs.get(k) is not None:
passthrough[k] = hdrs.get(k)
try:
req = urllib.request.Request(url, headers={"User-Agent": "godsigh-dev/0.1"})
with urllib.request.urlopen(req, timeout=45) as r:
body = r.read()
status, ctype = r.status, r.headers.get("Content-Type", "text/plain")
grab(r.headers)
except urllib.error.HTTPError as e:
body = e.read() or str(e).encode()
status, ctype = e.code, "text/plain"
grab(e.headers)
except Exception as e:
body, status, ctype = str(e).encode(), 502, "text/plain"
self.send_response(status)
self.send_header("Content-Type", ctype)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Expose-Headers", "X-Rate-Limit-Remaining, X-Rate-Limit-Limit, X-Expires-After")
self.send_header("Cache-Control", "no-store")
for k, v in passthrough.items():
self.send_header(k, v)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
sys.stderr.write("%s - %s\n" % (self.address_string(), fmt % args))
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8137
print(f"GODSIGH serving on http://127.0.0.1:{port}")
ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()