Spaces:
Sleeping
Sleeping
File size: 4,398 Bytes
9684770 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | """
============================================
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()
|