- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
136 lines
4.2 KiB
Python
136 lines
4.2 KiB
Python
"""
|
||
HTTP Client Manager - 永久連線池管理
|
||
=====================================
|
||
統帥鐵律: 禁止 subprocess+curl,必須用 httpx AsyncClient
|
||
|
||
Features:
|
||
- Lifespan 管理 (startup/shutdown)
|
||
- 連線池復用 (Connection Pooling)
|
||
- 強制 trust_env=False (禁止 HTTP_PROXY 干擾)
|
||
- ClickHouse/SignOz 專用 Client
|
||
"""
|
||
|
||
import httpx
|
||
import structlog
|
||
|
||
from src.core.config import settings
|
||
|
||
logger = structlog.get_logger(__name__)
|
||
|
||
|
||
# =============================================================================
|
||
# Singleton Clients
|
||
# =============================================================================
|
||
|
||
_clickhouse_client: httpx.AsyncClient | None = None
|
||
_general_client: httpx.AsyncClient | None = None
|
||
|
||
|
||
# =============================================================================
|
||
# ClickHouse Client (SignOz Backend)
|
||
# =============================================================================
|
||
|
||
async def get_clickhouse_client() -> httpx.AsyncClient:
|
||
"""
|
||
取得 ClickHouse HTTP Client
|
||
|
||
配置:
|
||
- base_url: 192.168.0.188:8123 (ClickHouse HTTP API)
|
||
- trust_env: False (禁止 HTTP_PROXY 干擾)
|
||
- timeout: 30 秒
|
||
- 連線池: limits=100
|
||
"""
|
||
global _clickhouse_client
|
||
if _clickhouse_client is None or _clickhouse_client.is_closed:
|
||
_clickhouse_client = httpx.AsyncClient(
|
||
base_url=settings.CLICKHOUSE_URL.rstrip("/"),
|
||
timeout=httpx.Timeout(30.0, connect=10.0),
|
||
trust_env=False, # 🔧 關鍵: 禁止讀取 HTTP_PROXY
|
||
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
||
headers={
|
||
"Content-Type": "text/plain", # ClickHouse 需要 plain text
|
||
},
|
||
)
|
||
logger.info(
|
||
"clickhouse_client_initialized",
|
||
base_url=settings.CLICKHOUSE_URL,
|
||
trust_env=False,
|
||
)
|
||
return _clickhouse_client
|
||
|
||
|
||
async def init_clickhouse_client() -> httpx.AsyncClient:
|
||
"""
|
||
初始化 ClickHouse Client (在 Lifespan 啟動時調用)
|
||
"""
|
||
return await get_clickhouse_client()
|
||
|
||
|
||
async def close_clickhouse_client() -> None:
|
||
"""
|
||
關閉 ClickHouse Client (在 Lifespan 關閉時調用)
|
||
"""
|
||
global _clickhouse_client
|
||
if _clickhouse_client and not _clickhouse_client.is_closed:
|
||
await _clickhouse_client.aclose()
|
||
logger.info("clickhouse_client_closed")
|
||
_clickhouse_client = None
|
||
|
||
|
||
# =============================================================================
|
||
# General HTTP Client
|
||
# =============================================================================
|
||
|
||
async def get_general_client() -> httpx.AsyncClient:
|
||
"""
|
||
取得通用 HTTP Client (Ollama, Gemini, Claude)
|
||
"""
|
||
global _general_client
|
||
if _general_client is None or _general_client.is_closed:
|
||
_general_client = httpx.AsyncClient(
|
||
timeout=httpx.Timeout(float(settings.OPENCLAW_TIMEOUT), connect=10.0),
|
||
trust_env=False,
|
||
limits=httpx.Limits(max_connections=50, max_keepalive_connections=10),
|
||
)
|
||
logger.info(
|
||
"general_client_initialized",
|
||
timeout=settings.OPENCLAW_TIMEOUT,
|
||
)
|
||
return _general_client
|
||
|
||
|
||
async def init_general_client() -> httpx.AsyncClient:
|
||
"""初始化通用 Client"""
|
||
return await get_general_client()
|
||
|
||
|
||
async def close_general_client() -> None:
|
||
"""關閉通用 Client"""
|
||
global _general_client
|
||
if _general_client and not _general_client.is_closed:
|
||
await _general_client.aclose()
|
||
logger.info("general_client_closed")
|
||
_general_client = None
|
||
|
||
|
||
# =============================================================================
|
||
# All Clients Lifecycle
|
||
# =============================================================================
|
||
|
||
async def init_all_http_clients() -> None:
|
||
"""
|
||
初始化所有 HTTP Clients (在 Lifespan 調用)
|
||
"""
|
||
await init_clickhouse_client()
|
||
await init_general_client()
|
||
logger.info("all_http_clients_initialized")
|
||
|
||
|
||
async def close_all_http_clients() -> None:
|
||
"""
|
||
關閉所有 HTTP Clients (在 Lifespan 調用)
|
||
"""
|
||
await close_clickhouse_client()
|
||
await close_general_client()
|
||
logger.info("all_http_clients_closed")
|