Ruhi_hosting / core /reverse_proxy.py
Ruhivig65's picture
Upload 11 files
9684770 verified
"""
============================================
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()