Files
IPSExtractor/services/api_gateway/api/main.py
T

158 lines
5.0 KiB
Python

"""Gateway HTTP sin compartir codigo interno de los microservicios."""
from __future__ import annotations
import os
from typing import Any
import httpx
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
EXTRACTION_URL = os.getenv("EXTRACTION_URL", "http://extraction:8001").rstrip("/")
CLEANING_URL = os.getenv("CLEANING_URL", "http://cleaning:8002").rstrip("/")
COUNT_URL = os.getenv("COUNT_URL", "http://count-api:8080").rstrip("/")
app = FastAPI(title="IPSExtractor API Gateway", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$",
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
async def health() -> dict[str, Any]:
async with httpx.AsyncClient(timeout=10.0) as client:
extraction = await _safe_health(client, EXTRACTION_URL)
cleaning = await _safe_health(client, CLEANING_URL)
counting = await _safe_health(client, COUNT_URL)
return {
"status": "ok" if extraction["ok"] and cleaning["ok"] else "degraded",
"services": {
"extraction": extraction,
"cleaning": cleaning,
"counting": counting,
},
}
@app.get("/models")
async def models() -> Response:
return await _proxy("GET", f"{EXTRACTION_URL}/models")
@app.get("/config")
async def config() -> dict[str, Any]:
async with httpx.AsyncClient(timeout=15.0) as client:
extraction = await _safe_json(client, f"{EXTRACTION_URL}/config")
cleaning_rules = await _safe_json(client, f"{CLEANING_URL}/rules")
return {
"extraction": extraction,
"cleaning": {"rules": cleaning_rules},
}
@app.api_route("/extraction/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def extraction_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/{path}")
@app.api_route("/tasks", methods=["GET"])
async def tasks_root_proxy(request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/tasks")
@app.api_route("/tasks/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def tasks_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/tasks/{path}")
@app.api_route("/cleaning/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def cleaning_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{CLEANING_URL}/{path}")
@app.api_route("/count/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def count_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{COUNT_URL}/{path}")
@app.api_route("/count-jobs", methods=["GET", "POST"])
async def count_jobs_root_proxy(request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/count-jobs")
@app.api_route("/count-jobs/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def count_jobs_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/count-jobs/{path}")
@app.api_route("/jobs", methods=["GET"])
async def jobs_root_proxy(request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/jobs")
@app.api_route("/jobs/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def jobs_proxy(path: str, request: Request) -> Response:
return await _proxy_request(request, f"{EXTRACTION_URL}/jobs/{path}")
async def _safe_health(client: httpx.AsyncClient, base_url: str) -> dict[str, Any]:
try:
response = await client.get(f"{base_url}/health")
return {"ok": response.is_success, "status_code": response.status_code}
except Exception as exc:
return {"ok": False, "error": str(exc)}
async def _safe_json(client: httpx.AsyncClient, url: str) -> Any:
try:
response = await client.get(url)
response.raise_for_status()
return response.json()
except Exception as exc:
return {"error": str(exc)}
async def _proxy(method: str, url: str) -> Response:
async with httpx.AsyncClient(timeout=60.0) as client:
upstream = await client.request(method, url)
return _to_response(upstream)
async def _proxy_request(request: Request, url: str) -> Response:
body = await request.body()
headers = {
key: value
for key, value in request.headers.items()
if key.lower() not in {"host", "content-length"}
}
async with httpx.AsyncClient(timeout=None) as client:
upstream = await client.request(
request.method,
url,
params=request.query_params,
content=body,
headers=headers,
)
return _to_response(upstream)
def _to_response(upstream: httpx.Response) -> Response:
excluded = {"content-encoding", "transfer-encoding", "connection"}
headers = {
key: value
for key, value in upstream.headers.items()
if key.lower() not in excluded
}
return Response(
content=upstream.content,
status_code=upstream.status_code,
headers=headers,
media_type=upstream.headers.get("content-type"),
)