Skip to content

[BUG] DashMiddleware crashes on WebSocket scopes (FastAPI backend) #3636

@benweinberg89

Description

@benweinberg89

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 scopes

The 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)
    return

Or, 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions