File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 55from importlib .resources import files
66from pathlib import Path
77
8+ # Interval for SSE heartbeat comments. Browsers cap concurrent HTTP/1.1
9+ # connections per origin (~6), and an SSE connection holds one slot. If stale
10+ # connections aren't detected promptly, rapid navigation can queue requests
11+ # behind them for tens of seconds. Heartbeats surface a broken pipe on write,
12+ # so stale clients are dropped quickly.
13+ SSE_HEARTBEAT_SECONDS = 10.0
14+
815_RELOAD_JS = files ("rockgarden.server" ).joinpath ("_live_reload.js" ).read_text ("utf-8" )
916_RELOAD_SCRIPT = f"\n <script>{ _RELOAD_JS } </script>\n "
1017_RELOAD_SCRIPT_BYTES = _RELOAD_SCRIPT .encode ("utf-8" )
@@ -142,15 +149,24 @@ def _handle_sse(self) -> None:
142149 self .end_headers ()
143150
144151 sse_clients .add (self )
152+ # Short socket timeout so read(1) returns periodically, letting us
153+ # send heartbeat comments. A failed heartbeat write means the
154+ # client is gone and this connection can release its slot.
155+ self .connection .settimeout (SSE_HEARTBEAT_SECONDS )
145156 try :
146- # Keep connection open until client disconnects
147157 while True :
148- # Block on read — will raise when client disconnects
149- data = self .rfile .read (1 )
150- if not data :
158+ try :
159+ data = self .rfile .read (1 )
160+ if not data :
161+ break
162+ except TimeoutError :
163+ try :
164+ self .wfile .write (b": keepalive\n \n " )
165+ self .wfile .flush ()
166+ except Exception :
167+ break
168+ except Exception :
151169 break
152- except Exception :
153- pass
154170 finally :
155171 sse_clients .remove (self )
156172
You can’t perform that action at this time.
0 commit comments