Serve the folder's replays until interrupted, opening the index in a browser.
(folder: Path, tour: TournamentInfo, renderer: ReplayRenderer, port: int = 8000)
| 24 | |
| 25 | |
| 26 | def run_server(folder: Path, tour: TournamentInfo, renderer: ReplayRenderer, port: int = 8000) -> None: |
| 27 | """Serve the folder's replays until interrupted, opening the index in a browser.""" |
| 28 | |
| 29 | def render_game(game) -> str: |
| 30 | payload = { |
| 31 | "arena": tour.arena, |
| 32 | "players": tour.players, |
| 33 | "round": game.round, |
| 34 | "sim": game.sim, |
| 35 | "round_winner": game.winner if game.winner is not None else tour.round_winners.get(game.round), |
| 36 | "rounds": tour.rounds, |
| 37 | "sims": tour.sims_per_round, |
| 38 | "games": len(tour.games), |
| 39 | "folder": tour.folder.name, |
| 40 | "matchup": game.group or None, |
| 41 | } |
| 42 | return base.build_page(renderer.parse(base.read_sim(game), tour.players), payload, renderer) |
| 43 | |
| 44 | def error_page(game, exc) -> str: |
| 45 | source = game.member or (game.path.name if game.path else "?") |
| 46 | return ( |
| 47 | '<!doctype html><meta charset="utf-8">' |
| 48 | "<body style='background:#0d1117;color:#e6edf3;font:14px system-ui;padding:24px'>" |
| 49 | f"<h2>Couldn't render round {game.round}, sim {game.sim}</h2>" |
| 50 | f"<p style='color:#8b949e'>source: <code>{html.escape(source)}</code></p>" |
| 51 | f"<pre style='color:#e5484d;white-space:pre-wrap'>{html.escape(type(exc).__name__)}: {html.escape(str(exc))}</pre>" |
| 52 | "<p><a style='color:#58a6ff' href='/'>← back to index</a></p></body>" |
| 53 | ) |
| 54 | |
| 55 | class Handler(BaseHTTPRequestHandler): |
| 56 | def do_GET(self): |
| 57 | parsed = urllib.parse.urlparse(self.path) |
| 58 | if parsed.path in ("/", "/index.html"): |
| 59 | self._html(base.build_index(tour)) |
| 60 | return |
| 61 | if parsed.path == "/game": |
| 62 | q = urllib.parse.parse_qs(parsed.query) |
| 63 | try: |
| 64 | idx = int(q["g"][0]) |
| 65 | game = tour.games[idx] |
| 66 | except (KeyError, ValueError, IndexError): |
| 67 | self.send_error(404, "no such game") |
| 68 | return |
| 69 | try: |
| 70 | self._html(render_game(game)) |
| 71 | except Exception as exc: # one bad game shouldn't take down the server |
| 72 | print(f"[replay] failed to render game {idx}: {type(exc).__name__}: {exc}") |
| 73 | self._html(error_page(game, exc), status=500) |
| 74 | return |
| 75 | self.send_error(404) |
| 76 | |
| 77 | def _html(self, page: str, status: int = 200): |
| 78 | body = page.encode() |
| 79 | self.send_response(status) |
| 80 | self.send_header("Content-Type", "text/html; charset=utf-8") |
| 81 | self.send_header("Content-Length", str(len(body))) |
| 82 | self.end_headers() |
| 83 | self.wfile.write(body) |