127 lines
4.5 KiB
Python
127 lines
4.5 KiB
Python
"""Docling service proxy endpoints."""
|
|
|
|
import os
|
|
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("<L", gw_int))
|
|
return gateway_ip
|
|
except (FileNotFoundError, PermissionError, IndexError, ValueError) as err:
|
|
logger.warning("Could not read routing table: %s", err)
|
|
|
|
return None
|
|
|
|
|
|
def determine_docling_host() -> str:
|
|
"""Determine the host address used for docling health checks."""
|
|
container_type = detect_container_environment()
|
|
if container_type:
|
|
# Try HOST_DOCKER_INTERNAL env var first
|
|
container_host = get_container_host()
|
|
if container_host:
|
|
logger.info("Using container-aware host '%s'", container_host)
|
|
return container_host
|
|
|
|
# Try special hostnames (Docker Desktop and rootless podman)
|
|
import socket
|
|
for hostname in ["host.docker.internal", "host.containers.internal"]:
|
|
try:
|
|
socket.getaddrinfo(hostname, None)
|
|
logger.info("Using %s for container-to-host communication", hostname)
|
|
return hostname
|
|
except socket.gaierror:
|
|
logger.debug("%s not available", hostname)
|
|
|
|
# Try gateway IP detection (Docker on Linux)
|
|
gateway_ip = _get_gateway_ip_from_route()
|
|
if gateway_ip:
|
|
logger.info("Detected host gateway IP: %s", gateway_ip)
|
|
return gateway_ip
|
|
|
|
# Fallback to bridge IP
|
|
fallback_ip = guess_host_ip_for_containers(logger=logger)
|
|
logger.info("Falling back to container bridge host %s", fallback_ip)
|
|
return fallback_ip
|
|
|
|
# Running outside a container
|
|
logger.info("Running outside a container; using localhost")
|
|
return "localhost"
|
|
|
|
|
|
# Use explicit URL if provided, otherwise auto-detect host
|
|
_docling_url_override = os.getenv("DOCLING_SERVE_URL")
|
|
if _docling_url_override:
|
|
DOCLING_SERVICE_URL = _docling_url_override.rstrip("/")
|
|
HOST_IP = _docling_url_override # For display in health responses
|
|
logger.info("Using DOCLING_SERVE_URL override: %s", DOCLING_SERVICE_URL)
|
|
else:
|
|
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.
|
|
"""
|
|
health_url = f"{DOCLING_SERVICE_URL}/health"
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
health_url,
|
|
timeout=2.0
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
return JSONResponse({
|
|
"status": "healthy",
|
|
"host": HOST_IP
|
|
})
|
|
else:
|
|
logger.warning("Docling health check failed", url=health_url, status_code=response.status_code)
|
|
return JSONResponse({
|
|
"status": "unhealthy",
|
|
"message": f"Health check failed with status: {response.status_code}",
|
|
"host": HOST_IP
|
|
}, status_code=503)
|
|
|
|
except httpx.TimeoutException:
|
|
logger.warning("Docling health check timeout", url=health_url)
|
|
return JSONResponse({
|
|
"status": "unhealthy",
|
|
"message": "Connection timeout",
|
|
"host": HOST_IP
|
|
}, status_code=503)
|
|
except Exception as e:
|
|
logger.error("Docling health check failed", url=health_url, error=str(e))
|
|
return JSONResponse({
|
|
"status": "unhealthy",
|
|
"message": str(e),
|
|
"host": HOST_IP
|
|
}, status_code=503)
|