Handle client disconnect for large payloads (#701)
Catch `ClientDisconnect` in FastAPI worker to return HTTP 499 for large payloads and reduce noisy error logs. --- Linear Issue: [TOO-189](https://linear.app/arcadedev/issue/TOO-189/catch-clientdisconnect-and-return-499-for-large-payloads) <a href="https://cursor.com/background-agent?bcId=bc-f777f89b-d2bc-4c0c-bcb1-b76fcf601e05"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a> <a href="https://cursor.com/agents?id=bc-f777f89b-d2bc-4c0c-bcb1-b76fcf601e05"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Eric Gustin <eric@arcade.dev>
This commit is contained in:
parent
83ec80c08f
commit
e75eeb7e77
3 changed files with 24 additions and 2 deletions
|
|
@ -4,6 +4,8 @@ from typing import Any, Callable
|
|||
from fastapi import Depends, FastAPI, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from opentelemetry.metrics import Meter
|
||||
from starlette.requests import ClientDisconnect
|
||||
from starlette.responses import Response
|
||||
from starlette.routing import Mount
|
||||
|
||||
from arcade_serve.core.base import (
|
||||
|
|
@ -96,7 +98,13 @@ class FastAPIRouter(Router):
|
|||
if use_auth_for_route
|
||||
else None,
|
||||
) -> Any:
|
||||
body_str = await request.body()
|
||||
try:
|
||||
body_str = await request.body()
|
||||
except ClientDisconnect:
|
||||
# Client disconnected while reading request body (often due to large payloads)
|
||||
# Return HTTP 499 (Client Closed Request)
|
||||
return Response(status_code=499)
|
||||
|
||||
body_json = json.loads(body_str) if body_str else {}
|
||||
request_data = RequestData(
|
||||
path=request.url.path,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "arcade-serve"
|
||||
version = "3.1.3"
|
||||
version = "3.1.4"
|
||||
description = "Arcade Serve - Serving infrastructure for Arcade tools and workers"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Annotated
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from arcade_core.schema import ToolCallRequest, ToolContext, ToolReference
|
||||
|
|
@ -6,6 +7,7 @@ from arcade_serve.fastapi.worker import FastAPIWorker
|
|||
from arcade_tdk import tool
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from starlette.requests import ClientDisconnect
|
||||
|
||||
|
||||
@tool()
|
||||
|
|
@ -164,3 +166,15 @@ def test_call_tool_route_tool_not_found(client_no_auth, call_tool_payload):
|
|||
# Ideally, this might be a 404 or 400, but BaseWorker.call_tool raises ValueError
|
||||
# which isn't automatically mapped to a 4xx by FastAPI unless handled explicitly.
|
||||
# TODO fix this.
|
||||
|
||||
|
||||
def test_client_disconnect_returns_499(client_no_auth, call_tool_payload):
|
||||
"""Test that ClientDisconnect during body read returns HTTP 499."""
|
||||
# Mock request.body() to raise ClientDisconnect
|
||||
with patch("starlette.requests.Request.body", new_callable=AsyncMock) as mock_body:
|
||||
mock_body.side_effect = ClientDisconnect()
|
||||
|
||||
response = client_no_auth.post("/worker/tools/invoke", json=call_tool_payload)
|
||||
|
||||
# Verify that we get a 499 status code
|
||||
assert response.status_code == 499
|
||||
|
|
|
|||
Loading…
Reference in a new issue