hf-backend / main.py
IDKHowToCodeFr's picture
feat: pin semantic-backend v1.2, set /semantic route, and harden container permissions
677e796
Raw History Blame Contribute Delete
3.86 kB
import asyncio
import importlib
import logging
import os
from contextlib import asynccontextmanager
from typing import Callable
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from registry import PROJECT_REGISTRY
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("hf-backend")
loaded_projects: dict[str, str] = {}
failed_projects: dict[str, str] = {}
max_body_size = int(os.getenv("MAX_BODY_SIZE_BYTES", str(10 * 1024 * 1024)))
request_timeout = float(os.getenv("REQUEST_TIMEOUT_SECONDS", "60.0"))
class RequestLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_size: int, timeout: float):
super().__init__(app)
self.max_size = max_size
self.timeout = timeout
async def dispatch(self, request: Request, call_next: Callable) -> Response:
content_length = request.headers.get("content-length")
if content_length and int(content_length) > self.max_size:
return Response(content="Payload Too Large", status_code=413)
bytes_received = 0
original_receive = request._receive
async def limited_receive():
nonlocal bytes_received
message = await original_receive()
if message["type"] == "http.request":
bytes_received += len(message.get("body", b""))
if bytes_received > self.max_size:
raise HTTPException(status_code=413, detail="Payload Too Large")
return message
request._receive = limited_receive
try:
return await asyncio.wait_for(call_next(request), timeout=self.timeout)
except asyncio.TimeoutError:
return Response(content="Request Timeout", status_code=504)
app = FastAPI(
title="HF Backend",
docs_url=None,
redoc_url=None,
openapi_url=None,
)
app.add_middleware(RequestLimitMiddleware, max_size=max_body_size, timeout=request_timeout)
raw_origins = [o.strip() for o in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",") if o.strip()]
allowed_origins = [o for o in raw_origins if o != "*"]
if allowed_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def load_routers():
disabled_raw = [p.strip().strip("/") for p in os.getenv("DISABLE_PROJECTS", "").split(",") if p.strip()]
disabled_set = {p for p in disabled_raw}
for prefix, target in PROJECT_REGISTRY.items():
clean_prefix = prefix.strip("/")
if clean_prefix in disabled_set:
logger.warning(f"[{prefix}] Skipped: disabled by DISABLE_PROJECTS")
continue
try:
logger.info(f"[{prefix}] Importing {target}")
module_name, attr_name = target.split(":")
module = importlib.import_module(module_name)
router = getattr(module, attr_name)
app.include_router(router, prefix=prefix)
loaded_projects[prefix] = target
logger.info(f"[{prefix}] Successfully mounted at {prefix}")
except Exception as e:
failed_projects[prefix] = f"{type(e).__name__}: {str(e)}"
logger.error(f"[{prefix}] Failed to mount router: {e}", exc_info=True)
load_routers()
@app.get("/health")
def health():
if not failed_projects:
overall_status = "healthy"
elif loaded_projects:
overall_status = "degraded"
else:
overall_status = "unhealthy"
return {
"status": overall_status,
"loaded": loaded_projects,
"failed": failed_projects,
}
@app.get("/")
def root():
return health()