from urllib.parse import quote import httpx from fastapi import APIRouter, Request from fastapi.responses import Response, StreamingResponse from config import settings router = APIRouter(tags=["chatlog-proxy"]) HOP_BY_HOP_HEADERS = { "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", } def _copy_headers(headers: httpx.Headers) -> dict[str, str]: copied: dict[str, str] = {} for key, value in headers.items(): if key.lower() not in HOP_BY_HOP_HEADERS: copied[key] = value return copied async def _proxy_chatlog(request: Request, upstream_path: str) -> Response: query = request.url.query target = f"{settings.chatlog_base_url}{upstream_path}" if query: target = f"{target}?{query}" body = await request.body() headers = { key: value for key, value in request.headers.items() if key.lower() not in HOP_BY_HOP_HEADERS and key.lower() != "host" } async with httpx.AsyncClient(timeout=None, trust_env=False, follow_redirects=True) as client: upstream = await client.request( request.method, target, content=body if body else None, headers=headers, ) response_headers = _copy_headers(upstream.headers) return StreamingResponse( iter([upstream.content]), status_code=upstream.status_code, media_type=upstream.headers.get("content-type"), headers=response_headers, ) @router.api_route("/api/v1/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) async def proxy_api_v1(path: str, request: Request): return await _proxy_chatlog(request, f"/api/v1/{path}") async def _proxy_media(kind: str, path: str, request: Request): safe_path = "/".join(quote(part, safe="") for part in path.split("/")) return await _proxy_chatlog(request, f"/{kind}/{safe_path}") @router.api_route("/image/{path:path}", methods=["GET", "POST", "OPTIONS"]) async def proxy_image(path: str, request: Request): return await _proxy_media("image", path, request) @router.api_route("/voice/{path:path}", methods=["GET", "POST", "OPTIONS"]) async def proxy_voice(path: str, request: Request): return await _proxy_media("voice", path, request) @router.api_route("/video/{path:path}", methods=["GET", "POST", "OPTIONS"]) async def proxy_video(path: str, request: Request): return await _proxy_media("video", path, request) @router.api_route("/file/{path:path}", methods=["GET", "POST", "OPTIONS"]) async def proxy_file(path: str, request: Request): return await _proxy_media("file", path, request) @router.api_route("/data/{path:path}", methods=["GET", "POST", "OPTIONS"]) async def proxy_data(path: str, request: Request): return await _proxy_media("data", path, request)