84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
import httpx
|
|
from cachetools import TTLCache
|
|
from tenacity import retry, stop_after_attempt, wait_exponential
|
|
import urllib.parse
|
|
|
|
from backend.logger import setup_logger
|
|
from configs import (
|
|
JUSTICE_API_BASE,
|
|
HTTP_TIMEOUT,
|
|
HTTP_MAX_CONNECTIONS,
|
|
HTTP_MAX_KEEPALIVE,
|
|
CACHE_TTL,
|
|
CACHE_MAX_SIZE,
|
|
RETRY_ATTEMPTS,
|
|
RETRY_MAX_WAIT,
|
|
RETRY_MIN_WAIT,
|
|
RETRY_MULTIPLIER,
|
|
)
|
|
|
|
WAIT_EXPONENTIAL = wait_exponential(
|
|
multiplier=RETRY_MULTIPLIER,
|
|
min=RETRY_MIN_WAIT,
|
|
max=RETRY_MAX_WAIT
|
|
)
|
|
STOP_AFTER_ATTEMPT = stop_after_attempt(RETRY_ATTEMPTS)
|
|
CACHE = TTLCache(maxsize=CACHE_MAX_SIZE, ttl=CACHE_TTL)
|
|
TIMEOUT = httpx.Timeout(HTTP_TIMEOUT)
|
|
LIMITS = httpx.Limits(
|
|
max_connections=HTTP_MAX_CONNECTIONS,
|
|
max_keepalive_connections=HTTP_MAX_KEEPALIVE
|
|
)
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
def get_client() -> httpx.AsyncClient:
|
|
return httpx.AsyncClient(timeout=TIMEOUT, limits=LIMITS)
|
|
|
|
@retry(stop=STOP_AFTER_ATTEMPT, wait=WAIT_EXPONENTIAL)
|
|
async def http_request(route: str, params: dict | None, remove_keys: list[str] | None = None) -> dict:
|
|
"""Sends an HTTP GET request and returns the parsed JSON response."""
|
|
|
|
url = f"{JUSTICE_API_BASE}{route}"
|
|
|
|
params_tuple = tuple(sorted(params.items())) if params else ()
|
|
cache_key = f"{url}:{params_tuple}"
|
|
if cache_key in CACHE:
|
|
return CACHE[cache_key]
|
|
|
|
returned_url = url
|
|
|
|
try:
|
|
logger.info(f"[HTTP REQUEST] | {url} | params={params}")
|
|
async with get_client() as client:
|
|
if params:
|
|
query_string = urllib.parse.urlencode(
|
|
params,
|
|
doseq=True,
|
|
quote_via=urllib.parse.quote
|
|
)
|
|
final_url = f"{url}?{query_string}"
|
|
response = await client.get(final_url)
|
|
else:
|
|
response = await client.get(url)
|
|
|
|
response.raise_for_status()
|
|
logger.info(f"[HTTP SUCCESS] | {url} | status={response.status_code}")
|
|
|
|
returned_url = str(response.url)
|
|
data = response.json()
|
|
|
|
if remove_keys and isinstance(data, dict):
|
|
for key in remove_keys:
|
|
data.pop(key, None)
|
|
|
|
result = {"url": returned_url, "data": data}
|
|
CACHE[cache_key] = result
|
|
return result
|
|
|
|
except httpx.HTTPError as e:
|
|
logger.error(f"[HTTP ERROR] | {returned_url} | {str(e)}")
|
|
return {"error": "http_error", "detail": str(e)}
|
|
except Exception as e:
|
|
logger.error(f"[UNEXPECTED ERROR] | {returned_url} | {str(e)}")
|
|
return {"error": "unexpected_error", "detail": str(e)} |