Skip to content

Commit 77fa80b

Browse files
committed
fix: prevent re-sending http.response.start on streaming responses
send_wrapper was re-sending http.response.start on every body chunk, which breaks streaming responses like FileResponse that send multiple chunks. Now it only sends the start message once.
1 parent 5e2b9c1 commit 77fa80b

1 file changed

Lines changed: 19 additions & 12 deletions

File tree

slowapi/middleware.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def __init__(self, app: ASGIApp) -> None:
156156
self.error_response: Optional[Response] = None
157157
self.initial_message: Message = {}
158158
self.inject_headers = False
159+
self.started = False
159160

160161
async def send_wrapper(self, message: Message) -> None:
161162
if message["type"] == "http.response.start":
@@ -164,18 +165,24 @@ async def send_wrapper(self, message: Message) -> None:
164165
self.initial_message = message
165166

166167
elif message["type"] == "http.response.body":
167-
if self.error_response:
168-
self.initial_message["status"] = self.error_response.status_code
169-
170-
if self.inject_headers:
171-
headers = MutableHeaders(raw=self.initial_message["headers"])
172-
headers = self.limiter._inject_asgi_headers(
173-
headers, self.request.state.view_rate_limit
174-
)
175-
176-
# send the http.response.start message just before the http.response.body one,
177-
# now that the headers are updated
178-
await self.send(self.initial_message)
168+
if not self.started:
169+
# Only modify and send the initial message once, on the first body chunk.
170+
# Streaming responses (e.g. FileResponse) send multiple body chunks;
171+
# re-sending http.response.start on each would cause
172+
# "RuntimeError: ASGI flow error: Response already started".
173+
self.started = True
174+
175+
if self.error_response:
176+
self.initial_message["status"] = self.error_response.status_code
177+
178+
if self.inject_headers:
179+
headers = MutableHeaders(raw=self.initial_message["headers"])
180+
headers = self.limiter._inject_asgi_headers(
181+
headers, self.request.state.view_rate_limit
182+
)
183+
184+
await self.send(self.initial_message)
185+
179186
await self.send(message)
180187

181188
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:

0 commit comments

Comments
 (0)