""" Platform Gateway — Proxy helper and service routing. """ import json import urllib.request import urllib.error from config import _internal_ssl_ctx from database import get_db def proxy_request(target_url, method, headers, body=None, timeout=120): """Proxy a request to a backend service. Returns (status, response_headers, response_body).""" try: req = urllib.request.Request(target_url, data=body, method=method) for k, v in headers.items(): req.add_header(k, v) with urllib.request.urlopen(req, context=_internal_ssl_ctx, timeout=timeout) as resp: resp_body = resp.read() resp_headers = dict(resp.headers) return resp.status, resp_headers, resp_body except urllib.error.HTTPError as e: body = e.read() if e.fp else b'{}' return e.code, dict(e.headers), body except Exception as e: return 502, {"Content-Type": "application/json"}, json.dumps({"error": f"Service unavailable: {e}"}).encode() # ── Service routing ── SERVICE_MAP = {} # populated from DB at startup def load_service_map(): global SERVICE_MAP conn = get_db() rows = conn.execute("SELECT id, proxy_target FROM apps WHERE enabled = 1").fetchall() conn.close() SERVICE_MAP = {row["id"]: row["proxy_target"] for row in rows} def resolve_service(path): """Given /api/trips/foo, return ('trips', 'http://backend:8087', '/api/foo').""" # Path format: /api/{service_id}/... parts = path.split("/", 4) # ['', 'api', 'trips', 'foo'] if len(parts) < 3: return None, None, None service_id = parts[2] target = SERVICE_MAP.get(service_id) if not target: return None, None, None remainder = "/" + "/".join(parts[3:]) if len(parts) > 3 else "/" # Services that don't use /api prefix (Express apps, etc.) NO_API_PREFIX_SERVICES = {"inventory", "music", "budget"} SERVICE_PATH_PREFIX = {"reader": "/v1"} if service_id in SERVICE_PATH_PREFIX: backend_path = f"{SERVICE_PATH_PREFIX[service_id]}{remainder}" elif service_id in NO_API_PREFIX_SERVICES: backend_path = remainder elif remainder.startswith("/images/") or remainder.startswith("/documents/"): backend_path = remainder else: backend_path = f"/api{remainder}" return service_id, target, backend_path