feat: implement long polling for WebSocket proxy
- Updated handlePoll in main_js.go to wait up to 5 seconds for messages, checking every 50ms, instead of immediate retrieval - Refactored _startPolling in polyfill.js from interval-based polling every 100ms to continuous long polling - Improves efficiency by reducing server requests and enhancing responsiveness for message delivery
lemon-mint committed
Nov 1, 2025 at 02:24 UTC
c5732f8ee4853da3f6d4859b1c244ae934fe2466
2 files changed
+38
-24
cmd/webclient/main_js.go
+29
-2
@@ -400,9 +400,36 @@ func (p *Proxy) handlePoll(w http.ResponseWriter, r *http.Request, connID string
400
return
401
}
402
403
- // Get queued messages
404
- messages := wsConn.GetMessages()
403
+ // Long polling: wait up to 5 seconds for messages
404
+ timeout := time.NewTimer(5 * time.Second)
405
+ defer timeout.Stop()
406
407
+ ticker := time.NewTicker(50 * time.Millisecond)
408
+ defer ticker.Stop()
409
+
410
+ var messages []StreamMessage
411
+
412
+ for {
413
+ select {
414
+ case <-timeout.C:
415
+ // Timeout - return empty or existing messages
416
+ messages = wsConn.GetMessages()
417
+ goto respond
418
+
419
+ case <-ticker.C:
420
+ // Check for messages periodically
421
+ messages = wsConn.GetMessages()
422
+ if len(messages) > 0 {
423
+ goto respond
424
+ }
425
+
426
+ case <-r.Context().Done():
427
+ // Client disconnected
428
+ return
429
+ }
430
+ }
431
+
432
+respond:
433
// Check if connection is closed and cleanup if needed
434
if wsConn.IsClosed() && len(messages) > 0 {
435
// Check if close message is in the queue
cmd/webclient/polyfill.js
+9
-22
@@ -71,16 +71,10 @@
71
}
72
}
73
74
- _startPolling() {
75
- if (!this._connId || this._isClosed) return;
76
-
77
- // Poll every 100ms
78
- this._pollInterval = setInterval(async () => {
79
- if (this._isClosed) {
80
- clearInterval(this._pollInterval);
81
- return;
82
- }
83
-
74
+ async _startPolling() {
75
+ // Long polling: continuously fetch messages
76
+ // Server will wait up to 5 seconds before responding
77
+ while (!this._isClosed) {
78
try {
79
const response = await fetch(`/sw-cgi/websocket/poll/${this._connId}`, {
80
method: 'GET'
@@ -99,16 +93,12 @@
93
}
94
95
} catch (error) {
102
- console.error('Polling error:', error);
103
- this._handleError(error);
96
+ if (!this._isClosed) {
97
+ console.error('Polling error:', error);
98
+ this._handleError(error);
99
+ }
100
+ break;
101
}
105
- }, 100);
106
- }
107
-
108
- _stopPolling() {
109
- if (this._pollInterval) {
110
- clearInterval(this._pollInterval);
111
- this._pollInterval = null;
102
}
103
}
104
@@ -180,9 +170,6 @@
170
this._isClosed = true;
171
this.readyState = WebSocket.CLOSED;
172
183
- // Stop polling
184
- this._stopPolling();
185
-
173
// Create CloseEvent
174
const event = new CloseEvent('close', {
175
code: code,