fix(api-gateway): fix RuntimeError(Stream consumed) for multipart uploads#206
Conversation
…ploads Add pure ASGI BodyCachingMiddleware that buffers the full request body into scope["body_cache"] before the app sees it, with a replayable receive callable so File(...) form parsing still works. Update _parse_request_body in security.py to read from scope["body_cache"] instead of await request.body(), eliminating the stream-consumed race on /v1/vision/analyze. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
Format core/src hive files to satisfy pre-commit ruff-format and mypy hooks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
Summary
BodyCachingMiddlewareinmain.pythat reads the full request body upfront, stores it inscope["body_cache"], and replays it via a replayablereceivecallable — soFile(...)form parsing still works downstream_parse_request_bodyinsecurity.pyto read fromscope["body_cache"]instead ofawait request.body(), bypassing the consumed-stream issue entirelyawait request.body()fromrequest_id_middleware(Starlette'sBaseHTTPMiddlewarecreates a separateRequestobject, so the cached_bodywas never shared with the route handler)Root cause: On
POST /v1/vision/analyze, FastAPI'sFile(...)param handling callsrequest.form()→stream(), setting_stream_consumed = Truewithout populating_body. Any subsequentrequest.body()call in the security dependency then raisedRuntimeError("Stream consumed")→ 500.Test plan
api-gatewayVITE_MOCK_RWA=false) and verify[Gateway] 200 OKin logs — noRuntimeError: Stream consumed/v1/negotiate,/v1/search) still work (they don't useFile(...), so unaffected)scope["body_cache"]bytes vs what the frontend signed🤖 Generated with Claude Code