71 lines
2.0 KiB
Python
71 lines
2.0 KiB
Python
"""
|
|
Shared HTTP clients for performance.
|
|
|
|
Why:
|
|
- Creating a new httpx.AsyncClient per request is expensive (TLS handshakes, no connection reuse).
|
|
- For high-frequency lookups (RDAP), we keep one pooled AsyncClient per process.
|
|
|
|
Notes:
|
|
- Per-request timeouts can still be overridden in client.get(..., timeout=...).
|
|
- Call close_* on shutdown for clean exit (optional but recommended).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Optional
|
|
|
|
import httpx
|
|
|
|
_rdap_client: Optional[httpx.AsyncClient] = None
|
|
_rdap_client_lock = asyncio.Lock()
|
|
|
|
|
|
def _rdap_limits() -> httpx.Limits:
|
|
# Conservative but effective defaults (works well for bursty traffic).
|
|
return httpx.Limits(max_connections=50, max_keepalive_connections=20, keepalive_expiry=30.0)
|
|
|
|
|
|
def _rdap_timeout() -> httpx.Timeout:
|
|
# Overall timeout can be overridden per request.
|
|
return httpx.Timeout(15.0, connect=5.0)
|
|
|
|
|
|
async def get_rdap_http_client() -> httpx.AsyncClient:
|
|
"""
|
|
Get a shared httpx.AsyncClient for RDAP requests.
|
|
Safe for concurrent use within the same event loop.
|
|
"""
|
|
global _rdap_client
|
|
if _rdap_client is not None and not _rdap_client.is_closed:
|
|
return _rdap_client
|
|
|
|
async with _rdap_client_lock:
|
|
if _rdap_client is not None and not _rdap_client.is_closed:
|
|
return _rdap_client
|
|
|
|
_rdap_client = httpx.AsyncClient(
|
|
timeout=_rdap_timeout(),
|
|
follow_redirects=True,
|
|
limits=_rdap_limits(),
|
|
headers={
|
|
# Be a good citizen; many registries/redirectors are sensitive.
|
|
"User-Agent": "pounce/1.0 (+https://pounce.ch)",
|
|
"Accept": "application/rdap+json, application/json",
|
|
},
|
|
)
|
|
return _rdap_client
|
|
|
|
|
|
async def close_rdap_http_client() -> None:
|
|
"""Close the shared RDAP client (best-effort)."""
|
|
global _rdap_client
|
|
if _rdap_client is None:
|
|
return
|
|
try:
|
|
if not _rdap_client.is_closed:
|
|
await _rdap_client.aclose()
|
|
finally:
|
|
_rdap_client = None
|
|
|