RuntimeError
with custom middleware when client drops connection before request finished
#1527
-
The issueWhen using two or more custom middleware that subclass How to reproduceCreate the following file # example.py:
import asyncio
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Route
async def homepage(request):
await asyncio.sleep(5)
return JSONResponse({"hello": "world"})
routes = [
Route("/", homepage, name="homepage"),
]
class CustomHeaderMiddleware1(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
return await call_next(request)
class CustomHeaderMiddleware2(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
return await call_next(request)
all_middleware = [
Middleware(CustomHeaderMiddleware1),
Middleware(CustomHeaderMiddleware2),
]
app = Starlette(debug=True, routes=routes, middleware=all_middleware) Run server:
Send HTTP request and disconnect before response is received:
You then get the following error on the server:
Is there something we are doing wrong or is this an issue? |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments 14 replies
-
I have noticed exactly the same problem with I am currently using following middleware as a quickfix (this middleware needs to be registered first). The middleware basically just catches the # -*- encoding: utf-8 -*-
# ! python3
from __future__ import annotations
import logging
from http import HTTPStatus
from typing import Final
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
logger: Final = logging.getLogger(__name__)
class SuppressNoResponseReturnedMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
try:
response = await call_next(request)
except RuntimeError as e:
if await request.is_disconnected() and str(e) == "No response returned.":
logger.warning(
"Error `No response returned` detected. "
"At the same time we know that the client is disconnected "
"and not waiting for a response."
)
return Response(status_code=HTTPStatus.NO_CONTENT)
else:
raise
return response |
Beta Was this translation helpful? Give feedback.
-
Me and my team stumbled upon the same issue couple of days ago and implemented the same kind of middleware as a workaround (it has to be the last middleware that was installed by the call I even got a pull request ready with the correction and was about to start discussion on this but you beat me to it. Anyway, I think that the main issue here is that the error is misleading and below correction in the diff --git a/starlette/middleware/base.py b/starlette/middleware/base.py
index bfb4a54..4da49aa 100644
--- a/starlette/middleware/base.py
+++ b/starlette/middleware/base.py
@@ -2,7 +2,7 @@ import typing
import anyio
-from starlette.requests import Request
+from starlette.requests import Request, ClientDisconnect
from starlette.responses import Response, StreamingResponse
from starlette.types import ASGIApp, Receive, Scope, Send
@@ -42,6 +42,8 @@ class BaseHTTPMiddleware:
except anyio.EndOfStream:
if app_exc is not None:
raise app_exc
+ if await request.is_disconnected():
+ raise ClientDisconnect("Client disconnect while processing the request")
raise RuntimeError("No response returned.")
assert message["type"] == "http.response.start" |
Beta Was this translation helpful? Give feedback.
-
We have the same issue with
The workaround seems to work. |
Beta Was this translation helpful? Give feedback.
-
I got the same here: Requirements:
Exception:
|
Beta Was this translation helpful? Give feedback.
-
Quite a few people complaining about exactly this problem in different issue reports around Starlette, FastAPI and other frameworks using the underlying toolset. See for example: For many peeps it's really hard to relate what you see in this particular stack trace to this discussion, but indeed I also spotted this issue when client disconnects abruptly. I used @Kludex IMHO, instead of closing comments on resolved issues, it would be nice to let community to leave pointers to the correct remedy. Kudos to @kprzybyla for finding the workaround! 🙇 However, there seems to be a large number of cases, when this error will be hitting the screen, because provided workarounds doesn't handle all situations. For example: if you hit
Just in case, my
Also maybe important to note, it doesn't matter whether you add a custom middleware or not. I tried to run I'd love to contribute to the resolution of the issue, but I'm afraid I'm quite useless here, except nagging to open an issue so others would be aware of the problem 😊 NB: I would never look into Discussions if I wouldn't see @Kludex point out to do so in this comment: #1255 (comment) |
Beta Was this translation helpful? Give feedback.
-
To help here, you can try to see if the problem is solved using the If it doesn't help, see if the problem is solved using #1441. |
Beta Was this translation helpful? Give feedback.
-
I'm running the example provided above: import asyncio
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Route
async def homepage(request):
await asyncio.sleep(5)
return JSONResponse({"hello": "world"})
routes = [
Route("/", homepage, name="homepage"),
]
class CustomHeaderMiddleware1(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
return await call_next(request)
class CustomHeaderMiddleware2(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
return await call_next(request)
all_middleware = [
Middleware(CustomHeaderMiddleware1),
Middleware(CustomHeaderMiddleware2),
]
app = Starlette(debug=True, routes=routes, middleware=all_middleware) with: uvicorn main:app --reload --port 8001 And then I'm running: wrk -c 100 -d 1 http://0.0.0.0:8001/ To be sure, I've added a counter, to see how many failures because of disconnection. I've run this against Starlette from today, 30 days ago, and 120 days ago (master branch wise). It looks like there's no regression, the issue is the same. We never stop having this issue, it was always here. I don't see anything on FastAPI that could have caused this issue to come into light more easily. If someone has some more insights, please feel free to share. |
Beta Was this translation helpful? Give feedback.
-
This error is raised when the stream is closed due to I managed to make this error reproducible in 0.14.2 by partially emulating 0.15.0 logic: acjh@37dd8ac I have submitted PR #1706 to shield send "http.response.start" from cancellation. |
Beta Was this translation helpful? Give feedback.
-
This is fixed in #1715 |
Beta Was this translation helpful? Give feedback.
This is fixed in #1715