#!/usr/bin/env python3 """Pipeline Lab server — serves notebook UI on :3500, proxies API to sidecar :3200.""" import http.server import json import urllib.request import urllib.error PORT = 3500 SIDECAR = "http://localhost:3200" class LabHandler(http.server.BaseHTTPRequestHandler): def log_message(self, fmt, *args): pass # quiet def do_GET(self): if self.path == "/" or self.path == "": self._serve_ui() elif self.path.startswith("/lab/"): self._proxy("GET") else: self.send_error(404) def do_POST(self): if self.path.startswith("/lab/"): self._proxy("POST") else: self.send_error(404) def do_DELETE(self): if self.path.startswith("/lab/"): self._proxy("DELETE") else: self.send_error(404) def _proxy(self, method): """Proxy request to sidecar.""" url = SIDECAR + self.path body = None if method in ("POST", "PUT"): length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length) if length else None req = urllib.request.Request(url, data=body, method=method) req.add_header("Content-Type", self.headers.get("Content-Type", "application/json")) try: with urllib.request.urlopen(req, timeout=120) as resp: data = resp.read() self.send_response(resp.status) self.send_header("Content-Type", resp.headers.get("Content-Type", "application/json")) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(data) except urllib.error.HTTPError as e: self.send_response(e.code) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(e.read()) except Exception as e: self.send_response(502) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"error": str(e)}).encode()) def _serve_ui(self): self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(HTML.encode()) HTML = r"""