mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
fix: use shared httpx client to prevent premature closure in SSE streaming
The proxy was creating a temporary httpx.AsyncClient within an async context manager. When returning StreamingResponse for SSE endpoints, the client was being closed before the streaming generator could use it, causing "client has been closed" errors. This change introduces a shared httpx.AsyncClient that persists for the application lifecycle, properly cleaned up during shutdown. This also improves performance by reusing TCP connections across requests. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
logger.info(f"Proxying to LangGraph server at {config.langgraph_url}")
|
||||
yield
|
||||
logger.info("Shutting down API Gateway")
|
||||
# Close the shared HTTP client
|
||||
await proxy.close_http_client()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
|
||||
@@ -11,6 +11,31 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["proxy"])
|
||||
|
||||
# Shared httpx client for all proxy requests
|
||||
# This avoids creating/closing clients during streaming responses
|
||||
_http_client: httpx.AsyncClient | None = None
|
||||
|
||||
|
||||
def get_http_client() -> httpx.AsyncClient:
|
||||
"""Get or create the shared HTTP client.
|
||||
|
||||
Returns:
|
||||
The shared httpx AsyncClient instance.
|
||||
"""
|
||||
global _http_client
|
||||
if _http_client is None:
|
||||
_http_client = httpx.AsyncClient()
|
||||
return _http_client
|
||||
|
||||
|
||||
async def close_http_client() -> None:
|
||||
"""Close the shared HTTP client if it exists."""
|
||||
global _http_client
|
||||
if _http_client is not None:
|
||||
await _http_client.aclose()
|
||||
_http_client = None
|
||||
|
||||
|
||||
# Hop-by-hop headers that should not be forwarded
|
||||
EXCLUDED_HEADERS = {
|
||||
"host",
|
||||
@@ -76,7 +101,8 @@ async def proxy_request(request: Request, path: str) -> Response | StreamingResp
|
||||
if request.method not in ("GET", "HEAD"):
|
||||
body = await request.body()
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
client = get_http_client()
|
||||
|
||||
try:
|
||||
# First, make a non-streaming request to check content type
|
||||
response = await client.request(
|
||||
|
||||
Reference in New Issue
Block a user