Skip to content

fix(api-gateway): fix RuntimeError(Stream consumed) for multipart uploads#206

Merged
zaebee merged 2 commits into
mainfrom
chore/auth
Mar 20, 2026
Merged

fix(api-gateway): fix RuntimeError(Stream consumed) for multipart uploads#206
zaebee merged 2 commits into
mainfrom
chore/auth

Conversation

@zaebee

@zaebee zaebee commented Mar 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add pure ASGI BodyCachingMiddleware in main.py that reads the full request body upfront, stores it in scope["body_cache"], and replays it via a replayable receive callable — so File(...) form parsing still works downstream
  • Update _parse_request_body in security.py to read from scope["body_cache"] instead of await request.body(), bypassing the consumed-stream issue entirely
  • Remove the now-ineffective await request.body() from request_id_middleware (Starlette's BaseHTTPMiddleware creates a separate Request object, so the cached _body was never shared with the route handler)

Root cause: On POST /v1/vision/analyze, FastAPI's File(...) param handling calls request.form()stream(), setting _stream_consumed = True without populating _body. Any subsequent request.body() call in the security dependency then raised RuntimeError("Stream consumed") → 500.

Test plan

  • Deploy updated api-gateway
  • Drop an image in the UI (VITE_MOCK_RWA=false) and verify [Gateway] 200 OK in logs — no RuntimeError: Stream consumed
  • Verify JSON endpoints (/v1/negotiate, /v1/search) still work (they don't use File(...), so unaffected)
  • If 401 instead of 200: compare SHA-256 hex of scope["body_cache"] bytes vs what the frontend signed

🤖 Generated with Claude Code

…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>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 RuntimeError("Stream consumed") that occurred in the API Gateway when handling multipart uploads with FastAPI's File(...) parameters. The issue arose because File(...) would consume the ASGI stream, preventing subsequent security dependencies from reading the request body. The solution involves implementing a pure ASGI BodyCachingMiddleware that reads and caches the entire request body upfront, making it available to all downstream components, thereby ensuring both file uploads and security checks function correctly without stream consumption conflicts.

Highlights

  • Body Caching Middleware: Introduced BodyCachingMiddleware in main.py to proactively read and store the full request body in scope["body_cache"], ensuring it can be replayed for downstream components like File(...) form parsing.
  • Security Dependency Update: Modified _parse_request_body in security.py to retrieve the request body from scope["body_cache"], resolving the RuntimeError("Stream consumed") that occurred when File(...) parameters had already processed the stream.
  • Middleware Refinement: Removed the await request.body() call from request_id_middleware, as it was no longer effective due to Starlette's BaseHTTPMiddleware creating a separate Request object, and the new BodyCachingMiddleware now handles body persistence.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Format core/src hive files to satisfy pre-commit ruff-format and mypy hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zaebee
zaebee merged commit 6eb3a9f into main Mar 20, 2026
10 checks passed
@zaebee
zaebee deleted the chore/auth branch March 20, 2026 16:09
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant