Spaces:
Sleeping
Sleeping
| """ | |
| ============================================ | |
| RUHI-CORE - Built-in Reverse Proxy | |
| Routes traffic to internal service ports | |
| ============================================ | |
| """ | |
| import httpx | |
| from fastapi import Request, Response | |
| from fastapi.responses import StreamingResponse | |
| from loguru import logger | |
| from core.port_manager import port_manager | |
| from core.process_manager import process_manager | |
| class ReverseProxy: | |
| """ | |
| Reverse proxy to route traffic from /proxy/{service_name}/ | |
| to the internal port of the running service | |
| """ | |
| def __init__(self): | |
| self._client = httpx.AsyncClient( | |
| timeout=30.0, | |
| follow_redirects=True, | |
| limits=httpx.Limits( | |
| max_connections=100, | |
| max_keepalive_connections=20 | |
| ) | |
| ) | |
| logger.info("🔀 ReverseProxy initialized") | |
| async def proxy_request(self, service_name: str, path: str, request: Request) -> Response: | |
| """Proxy an incoming request to the target service""" | |
| # Find the service | |
| service = process_manager.get_service_by_name(service_name) | |
| if not service: | |
| return Response( | |
| content=f'{{"error": "Service \'{service_name}\' not found"}}', | |
| status_code=404, | |
| media_type="application/json" | |
| ) | |
| if service.status != "running": | |
| return Response( | |
| content=f'{{"error": "Service \'{service_name}\' is not running (status: {service.status})"}}', | |
| status_code=503, | |
| media_type="application/json" | |
| ) | |
| if not service.port: | |
| return Response( | |
| content=f'{{"error": "Service \'{service_name}\' has no port assigned"}}', | |
| status_code=503, | |
| media_type="application/json" | |
| ) | |
| # Build target URL | |
| target_url = f"http://127.0.0.1:{service.port}/{path}" | |
| # Build headers (forward relevant ones) | |
| headers = {} | |
| for key, value in request.headers.items(): | |
| if key.lower() not in ("host", "connection", "content-length", "transfer-encoding"): | |
| headers[key] = value | |
| headers["X-Forwarded-For"] = request.client.host | |
| headers["X-Forwarded-Proto"] = request.url.scheme | |
| headers["X-Forwarded-Host"] = request.headers.get("host", "") | |
| headers["X-Real-IP"] = request.client.host | |
| try: | |
| # Get request body | |
| body = await request.body() | |
| # Make proxied request | |
| response = await self._client.request( | |
| method=request.method, | |
| url=target_url, | |
| headers=headers, | |
| content=body, | |
| params=dict(request.query_params) | |
| ) | |
| # Build response headers | |
| resp_headers = dict(response.headers) | |
| # Remove hop-by-hop headers | |
| for h in ("connection", "keep-alive", "transfer-encoding", "content-encoding", "content-length"): | |
| resp_headers.pop(h, None) | |
| return Response( | |
| content=response.content, | |
| status_code=response.status_code, | |
| headers=resp_headers, | |
| media_type=response.headers.get("content-type") | |
| ) | |
| except httpx.ConnectError: | |
| return Response( | |
| content=f'{{"error": "Cannot connect to service \'{service_name}\' on port {service.port}"}}', | |
| status_code=502, | |
| media_type="application/json" | |
| ) | |
| except httpx.TimeoutException: | |
| return Response( | |
| content=f'{{"error": "Request to service \'{service_name}\' timed out"}}', | |
| status_code=504, | |
| media_type="application/json" | |
| ) | |
| except Exception as e: | |
| logger.error(f"Proxy error for {service_name}: {str(e)}") | |
| return Response( | |
| content=f'{{"error": "Proxy error: {str(e)}"}}', | |
| status_code=502, | |
| media_type="application/json" | |
| ) | |
| async def close(self): | |
| """Close the HTTP client""" | |
| await self._client.aclose() | |
| # Global instance | |
| reverse_proxy = ReverseProxy() | |