mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
195 lines
7.4 KiB
Python
195 lines
7.4 KiB
Python
import asyncio
|
|
import time
|
|
from fastapi import FastAPI, Request, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from contextlib import asynccontextmanager
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
import httpx
|
|
import logging
|
|
# Gateway doesn't need direct model access - it's a stateless proxy
|
|
# Gateway doesn't need constants - it's a stateless proxy
|
|
from backend.shared.utils import get_client_ip
|
|
|
|
# Gateway doesn't need database access - it's a stateless proxy
|
|
from backend.logging_config import access_logger # noqa: F401 - ensure loggers configured
|
|
from backend.security.audit import log_access
|
|
from backend.security.rate_limit import limiter
|
|
from slowapi.middleware import SlowAPIMiddleware
|
|
|
|
# Service URL mapping for routing
|
|
SERVICE_URLS = {
|
|
"account": os.getenv("ACCOUNT_SERVICE_URL", "http://account_service:8302"),
|
|
"profile": os.getenv("PROFILE_SERVICE_URL", "http://profile_service:8303"),
|
|
"devices": os.getenv("DEVICE_SERVICE_URL", "http://device_service:8304"),
|
|
"messaging": os.getenv("MESSAGING_SERVICE_URL", "http://messaging_service:8305"),
|
|
"push": os.getenv("PUSH_SERVICE_URL", "http://push_service:8306"),
|
|
"webrtc": os.getenv("WEBRTC_SERVICE_URL", "http://webrtc_service:8307"),
|
|
"moderation": os.getenv("MODERATION_SERVICE_URL", "http://moderation_service:8308"),
|
|
}
|
|
|
|
logger = logging.getLogger("uvicorn.error")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Gateway is a stateless proxy - no database operations or background tasks needed
|
|
logger.info("Gateway proxy service initialized - routing to microservices")
|
|
yield
|
|
logger.info("Gateway proxy service shutting down.")
|
|
|
|
# Инициализация FastAPI
|
|
app = FastAPI(title="FromChat", lifespan=lifespan)
|
|
|
|
# Add rate limiting middleware
|
|
app.state.limiter = limiter
|
|
app.add_middleware(SlowAPIMiddleware)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def access_logging_middleware(request: Request, call_next):
|
|
# Log incoming request and Authorization header presence for debugging auth issues
|
|
try:
|
|
auth_header = request.headers.get("authorization")
|
|
if auth_header:
|
|
short = auth_header[:20] + "..." if len(auth_header) > 20 else auth_header
|
|
logger.info("Incoming request %s %s Authorization=%s", request.method, request.url.path, short)
|
|
else:
|
|
logger.info("Incoming request %s %s Authorization=NONE", request.method, request.url.path)
|
|
except Exception:
|
|
pass
|
|
start = time.perf_counter()
|
|
try:
|
|
response = await call_next(request)
|
|
except Exception as exc:
|
|
duration = time.perf_counter() - start
|
|
user = getattr(getattr(request, "state", None), "current_user", None)
|
|
log_access(
|
|
"http_error",
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status="error",
|
|
user=getattr(user, "username", None),
|
|
ip=get_client_ip(request),
|
|
duration=f"{duration:.3f}s",
|
|
error=str(exc),
|
|
)
|
|
raise
|
|
else:
|
|
duration = time.perf_counter() - start
|
|
user = getattr(getattr(request, "state", None), "current_user", None)
|
|
log_access(
|
|
"http_request",
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status=response.status_code,
|
|
user=getattr(user, "username", None),
|
|
ip=get_client_ip(request),
|
|
duration=f"{duration:.3f}s",
|
|
)
|
|
return response
|
|
|
|
|
|
# CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[
|
|
"https://fromchat.ru",
|
|
"https://beta.fromchat.ru",
|
|
"https://www.fromchat.ru",
|
|
"http://127.0.0.1:8301",
|
|
"http://127.0.0.1:8300",
|
|
"http://localhost:8301",
|
|
"http://localhost:8300",
|
|
],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Common API endpoints - route to appropriate services (defined first for priority)
|
|
@app.api_route("/login", methods=["POST"])
|
|
async def login(request: Request):
|
|
"""Login endpoint - routes to account service."""
|
|
return await _proxy_to_service("account", "login", request)
|
|
|
|
@app.api_route("/register", methods=["POST"])
|
|
async def register(request: Request):
|
|
"""Register endpoint - routes to account service."""
|
|
return await _proxy_to_service("account", "register", request)
|
|
|
|
|
|
# API routes - route to appropriate microservices
|
|
@app.api_route("/account/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_account(path: str, request: Request):
|
|
"""Proxy account service requests."""
|
|
return await _proxy_to_service("account", path, request)
|
|
|
|
@app.api_route("/profile/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_profile(path: str, request: Request):
|
|
"""Proxy profile service requests."""
|
|
return await _proxy_to_service("profile", path, request)
|
|
|
|
@app.api_route("/devices/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_devices(path: str, request: Request):
|
|
"""Proxy device service requests."""
|
|
return await _proxy_to_service("devices", path, request)
|
|
|
|
@app.api_route("/messaging/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_messaging(path: str, request: Request):
|
|
"""Proxy messaging service requests."""
|
|
return await _proxy_to_service("messaging", path, request)
|
|
|
|
@app.api_route("/push/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_push(path: str, request: Request):
|
|
"""Proxy push service requests."""
|
|
return await _proxy_to_service("push", path, request)
|
|
|
|
@app.api_route("/webrtc/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_webrtc(path: str, request: Request):
|
|
"""Proxy WebRTC service requests."""
|
|
return await _proxy_to_service("webrtc", path, request)
|
|
|
|
@app.api_route("/moderation/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
|
async def proxy_moderation(path: str, request: Request):
|
|
"""Proxy moderation service requests."""
|
|
return await _proxy_to_service("moderation", path, request)
|
|
|
|
|
|
async def _proxy_to_service(service: str, path: str, request: Request):
|
|
"""Helper function to proxy requests to microservices."""
|
|
from fastapi.responses import Response
|
|
|
|
service_url = SERVICE_URLS[service]
|
|
target_url = f"{service_url}/{service}/{path}"
|
|
|
|
# Get request body
|
|
body = await request.body()
|
|
|
|
# Prepare headers (remove host header)
|
|
headers = dict(request.headers)
|
|
headers.pop("host", None)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.request(
|
|
method=request.method,
|
|
url=target_url,
|
|
headers=headers,
|
|
content=body,
|
|
params=request.query_params,
|
|
)
|
|
|
|
# Return response with the same status code and content
|
|
content = response.content
|
|
return Response(
|
|
content=content,
|
|
status_code=response.status_code,
|
|
headers={"content-type": response.headers.get("content-type", "application/json")}
|
|
)
|
|
except httpx.RequestError as exc:
|
|
logging.error(f"Error communicating with {service} service: {exc}")
|
|
raise HTTPException(status_code=503, detail=f"Service {service} unavailable")
|
|
|
|
# Routes are handled by the catch-all proxy above |