-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Describe your context
dash 4.1.0rc0
fastapi 0.135.1
starlette 1.0.0rc1
uvicorn 0.41.0
- Python 3.13.0
- OS: macOS 15.5
Describe the bug
DashMiddleware.__call__ in dash/backends/_fastapi.py crashes when a WebSocket connection is made to the underlying FastAPI server. The middleware allows "websocket" scopes through its type filter (line 187) but then unconditionally creates a Starlette Request(scope, receive=receive) on line 192. Starlette's Request.__init__ asserts scope["type"] == "http", so any WebSocket scope raises an AssertionError.
This prevents using FastAPI's native @app.server.websocket() decorator alongside Dash.
The relevant code in DashMiddleware.__call__:
# Non-HTTP/WebSocket scopes pass through
if scope["type"] not in ("http", "websocket"):
await self.app(scope, receive, send)
return
# HTTP/WebSocket request handling
request = Request(scope, receive=receive) # crashes for websocket scopesThe comment says "pass through" for non-HTTP/WebSocket, but WebSocket scopes should also pass through since Request() only supports HTTP.
Expected behavior
WebSocket connections to custom FastAPI endpoints should work without crashing. The middleware should either pass WebSocket scopes through to the underlying ASGI app, or handle them with the appropriate Starlette WebSocket class instead of Request.
Suggested fix
Either change the guard to only process HTTP scopes:
if scope["type"] != "http":
await self.app(scope, receive, send)
returnOr, if the middleware needs to handle WebSocket scopes in the future, use WebSocket(scope, receive=receive, send=send) for websocket types.
Reproduction
import dash
from dash import html
from fastapi import WebSocket, WebSocketDisconnect
app = dash.Dash(__name__, backend="fastapi")
app.layout = html.Div("Hello")
@app.server.websocket("/ws")
async def ws_endpoint(websocket: WebSocket):
await websocket.accept()
await websocket.send_json({"msg": "hello"})
await websocket.close()
if __name__ == "__main__":
app.run(debug=True, port=8050)Connect with any WebSocket client → AssertionError inside Starlette's Request.__init__.
Workaround
Monkey-patch the middleware to pass WebSocket scopes straight through:
from dash.backends._fastapi import DashMiddleware
_original_call = DashMiddleware.__call__
async def _patched_call(self, scope, receive, send):
if scope["type"] == "websocket":
await self.app(scope, receive, send)
return
return await _original_call(self, scope, receive, send)
DashMiddleware.__call__ = _patched_call