Skip to content

Open FastAPI/Quart websocket only when a websocket callback needs it - #3941

Open
T4rk1n wants to merge 2 commits into
devfrom
fix/ws-always-on
Open

Open FastAPI/Quart websocket only when a websocket callback needs it#3941
T4rk1n wants to merge 2 commits into
devfrom
fix/ws-always-on

Conversation

@T4rk1n

@T4rk1n T4rk1n commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Closes #3939

Problem

The FastAPI and Quart backends always advertise WebSocket infrastructure (url/worker_url) in the page config, because they have websocket_capability = True regardless of whether any WebSocket callback exists. The renderer keyed its "open the socket" decision on the mere presence of that infra, so every app on these backends opened a WebSocket on page load — even apps with no WebSocket callbacks at all. config.websocket.enabled (bool(self._websocket_callbacks)) was already computed but ignored for the connect decision.

Repro: a plain Dash(backend="fastapi") app with only HTTP callbacks opens a socket on load (WebSocket /_dash-ws-callback [accepted]).

Fix (renderer only)

Split "register handlers" from "open the socket":

  • observers/websocketObserver.tsinitializeWebSocket still registers all message handlers (onSetProps, onGetPropsRequest, onConnected, …) whenever the infra is present, but only opens the socket eagerly when config.websocket.enabled is true. Keeping handlers registered is what lets the lazy path work: a per-callback websocket=True opens the socket on first dispatch via the existing handleWebsocketCallback → workerClient.ensureConnected path, and the handlers are already there to route server-pushed set_props/get_props.
  • Guarded the visibilitychange reconnect with wasDisconnected, so tab focus never opens a first connection for an app that never had one.
  • AppProvider.react.tsx — the effect now registers the observer whenever the infra is available; the enabled/lazy decision lives entirely inside initializeWebSocket.

Resulting behavior

Setup Connects
websocket_callbacks=True (global) eagerly on load (unchanged)
only per-callback websocket=True lazily, on first such dispatch
no WebSocket callbacks never

Tests

tests/websocket/test_ws_lazy_connect.py adds three regression tests that use a websocket_connect hook as a precise server-side probe for "did a socket actually open": never-connect (HTTP only), lazy-connect (per-callback), eager-connect (global). Verified they fail on the pre-fix bundle (the never/lazy cases saw a socket open on load) and pass with the fix. Existing test_ws_basic.py + test_ws_hooks.py (15 tests) still pass.

The FastAPI and Quart backends always advertise websocket infrastructure
(url/worker_url) in the page config, and the renderer keyed the connect
decision on the mere presence of that infra, so every app on these backends
opened a socket on page load even with no websocket callbacks.

Register the websocket message handlers whenever the infra is present, but
only open the socket eagerly when websocket_callbacks=True (config.enabled).
Per-callback websocket=True opens it lazily on first dispatch via the existing
ensureConnected path; an app with no websocket callbacks never connects. Also
guard the visibility reconnect so it never opens a first connection.

Add regression tests using a websocket_connect hook as a server-side probe:
never-connect (HTTP only), lazy-connect (per-callback), eager-connect (global).
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Websocket connection initiated by front end even if not enabled in back end

2 participants