Checking if a request is disconnected fails when using BaseHTTPMiddleware
.
#2094
Replies: 9 comments 15 replies
-
Maybe related to #2093 |
Beta Was this translation helpful? Give feedback.
-
We've stumbled upon the same problem. Downgrading to 0.20.4 fixes the problem. Not sure if this would be useful at all, but before finding this thread, I have downgraded to 0.14.2 (the version when it worked for us), which is the last version of Starlette before the migration to |
Beta Was this translation helpful? Give feedback.
-
I tried translating your example to use |
Beta Was this translation helpful? Give feedback.
-
same issue on the latest version: 0.31.1 . |
Beta Was this translation helpful? Give feedback.
-
Can someone create a table of affected versions for every Starlette version from 0.14.0 to current, and considering AnyIO versions 3 and 4 as well? Also, was this reintroduced? I don't have bandwidth to investigate this for the time being, but I appreciate help. If confirmed, I can change the priority in my backlog. |
Beta Was this translation helpful? Give feedback.
-
I think the issue is related to this section of code in the is_disconnected(self) method of the request.
I am using python the issue is that the changing the code to
resolved the issue. |
Beta Was this translation helpful? Give feedback.
-
I am experiencing the same issue. Client disconnect is not detected for an app with some middleware. It works without middleware enabled. It also works with middleware enabled for older versions as @weditor pointed out. starlette 0.27.0 How can this be escalated as an issue (from potential issue)? |
Beta Was this translation helpful? Give feedback.
-
End-users wanting a quick until starlette fixes it upstream can use this helper instead of calling async def my_is_disconnected(request: Request) -> bool:
assert hasattr(request, '_is_disconnected'), "private API in starlette changed"
assert isinstance(request._is_disconnected, bool), "private API in starlette changed"
if request._is_disconnected:
return True
message: Message = {}
# this timeout may need to be tweaked.
# Ideally request.receive() is non-blocking, in which case the timeout doesn't matter.
# But the ASGI spec seems to imply it should be a blocking callable, as it doesn't specify
# what should be returned in case of no new messages. In this situation you can return an
# empty dict, but given that it's against the spec it seems inadvisable.
with anyio.move_on_after(0.01):
message = await request.receive()
if message.get("type") == "http.disconnect":
request._is_disconnected = True
return request._is_disconnected Alternatively if you control the receive callable you can also edit/wrap it such that it first tries to return a receivable, then checkpoints, and only then does an actual blocking wait. That way you will never have a slowdown from the timeout in the above workaround. From my reading of the ASGI spec it implies that the callable should be a blocking wait*, so the only way I could see starlette never needing to have a timeout at all would be to rewrite the code in * https://github.com/django/asgiref/blob/main/specs/asgi.rst says
and the types only specifies valid messages as return objects. |
Beta Was this translation helpful? Give feedback.
-
@jakkdl 's solution offered a clever workaround that effectively addresses the current issue. However, I'll also retain the temporary solution I've been using for documentation purposes. class MyMiddleware(BaseHTTPMiddleware):
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
request.state.is_disconnected = request.is_disconnected
response = await call_next(request)
return response
# app.add_middleware(... other middlewares ...)
app.add_middleware(MyMiddleware)
@app.get("/")
async def hello(request: Request) -> Optional[Dict[str, Any]]:
for _ in range(10):
if await request.state.is_disconnected():
print("Client is disconnected.")
return None
else:
print("Client is connected.")
await asyncio.sleep(1)
return {"Hello": "World"} In summary, it involves using the state as specified in the ASGI spec to share the For reference, the ASGI doc describes the state as follows:
|
Beta Was this translation helpful? Give feedback.
-
I use FastAPI where I have some route that does some long work which becomes useless when the client disconnects. I want to cancel the request when the client disconnects. I used some code similar to the code below which worked for me in the past. I tried to configured some middleware which broke the code. The following code works fine when using starlette before version
0.21.0
or when removing the middleware.The following client code can be used to check the behaviour.
The output when using starlette before version
0.21.0
or when removing the middleware should be similar to the following (expected behaviour):The output when using starlette after version
0.21.0
with the middleware should be similar to the following (unexpected behaviour):Beta Was this translation helpful? Give feedback.
All reactions