diff --git a/src/api/docling.py b/src/api/docling.py new file mode 100644 index 00000000..66b7777e --- /dev/null +++ b/src/api/docling.py @@ -0,0 +1,104 @@ +"""Docling service proxy endpoints.""" + +import socket +import struct +from pathlib import Path + +import httpx +from starlette.requests import Request +from starlette.responses import JSONResponse + +from utils.container_utils import ( + detect_container_environment, + get_container_host, + guess_host_ip_for_containers, +) +from utils.logging_config import get_logger + +logger = get_logger(__name__) + + +def _get_gateway_ip_from_route() -> str | None: + """Return the default gateway IP visible from the current network namespace.""" + try: + with Path("/proc/net/route").open() as route_table: + next(route_table) # Skip header + for line in route_table: + fields = line.strip().split() + min_fields = 3 # interface, destination, gateway + if len(fields) >= min_fields and fields[1] == "00000000": + gateway_hex = fields[2] + gw_int = int(gateway_hex, 16) + gateway_ip = socket.inet_ntoa(struct.pack(" str: + """Determine the host address used for docling health checks.""" + container_type = detect_container_environment() + if container_type: + container_host = get_container_host() + if container_host: + logger.info("Using container-aware host '%s'", container_host) + return container_host + + gateway_ip = _get_gateway_ip_from_route() + if gateway_ip: + logger.info("Detected host gateway IP: %s", gateway_ip) + return gateway_ip + + # Either we're not inside a container or gateway detection failed. + fallback_ip = guess_host_ip_for_containers(logger=logger) + if container_type: + logger.info("Falling back to container bridge host %s", fallback_ip) + else: + logger.info("Running outside a container; using host %s", fallback_ip) + return fallback_ip + + +# Detect the host IP once at startup +HOST_IP = determine_docling_host() +DOCLING_SERVICE_URL = f"http://{HOST_IP}:5001" + + +async def health(request: Request) -> JSONResponse: + """ + Proxy health check to docling-serve. + This allows the frontend to check docling status via same-origin request. + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"{DOCLING_SERVICE_URL}/health", + timeout=2.0 + ) + + if response.status_code == 200: + return JSONResponse({ + "status": "healthy", + "host": HOST_IP + }) + else: + return JSONResponse({ + "status": "unhealthy", + "message": f"Health check failed with status: {response.status_code}", + "host": HOST_IP + }, status_code=503) + + except httpx.TimeoutException: + return JSONResponse({ + "status": "unhealthy", + "message": "Connection timeout", + "host": HOST_IP + }, status_code=503) + except Exception as e: + logger.error("Docling health check failed", error=str(e)) + return JSONResponse({ + "status": "unhealthy", + "message": str(e), + "host": HOST_IP + }, status_code=503)