mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
v0.4.3 : anti-buffering SSE (Cloudflare) - fin des derniers messages en retard au refresh
Le chat E2E d'app.ajean.link traverse Cloudflare (ajean.link, proxy orange) qui bufferise le flux SSE par lots : la queue du replay arrivait 20-30s apres le reste, de facon intermittente (surtout Safari iOS). Corrige par la technique standard : padding d'amorcage en tete de flux + padding sur caught_up (force le flush de la queue), Cache-Control: no-transform (interdit a Cloudflare de bufferiser/compresser), et heartbeat 15s->4s (borne le pire cas).
This commit is contained in:
1 parent
7d104e3213
commit
51c2c2d80f
7 files changed
+25
-8
No files matched your search
+14
-1
@@ -311,6 +311,16 @@ func (c *Conversation) Subscribe(ctx context.Context, from int, emit func(map[st
|
||||
c.mu.Unlock()
|
||||
}()
|
||||
|
||||
// 0. Amorçage anti-buffering. Le chat E2E d'app.ajean.link traverse Cloudflare
|
||||
// (ajean.link, proxy orange) : tant qu'un proxy intermédiaire n'a pas reçu assez
|
||||
// d'octets, il bufferise la réponse et ne la relaie qu'en retard (symptôme :
|
||||
// derniers messages qui arrivent 20-30 s après le reste, de façon intermittente
|
||||
// sur Safari). Envoyer d'emblée un gros événement de padding force le proxy à
|
||||
// basculer en mode streaming tout de suite. Le client ignore la clé `pad`.
|
||||
if !emit(map[string]any{"pad": strings.Repeat("·", 2048)}) {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Replay coalescé (snapshot hors verrou pour ne pas bloquer la génération).
|
||||
c.mu.Lock()
|
||||
snapshot := append([]LogEvent(nil), c.Log...)
|
||||
@@ -328,7 +338,10 @@ func (c *Conversation) Subscribe(ctx context.Context, from int, emit func(map[st
|
||||
last = s
|
||||
}
|
||||
}
|
||||
if !emit(map[string]any{"caught_up": true}) {
|
||||
// Padding aussi sur caught_up : force le flush de la QUEUE du replay à travers un
|
||||
// proxy qui re-bufferise par lots (Cloudflare) → les derniers messages arrivent
|
||||
// avec le reste, plus 20-30 s après.
|
||||
if !emit(map[string]any{"caught_up": true, "pad": strings.Repeat("·", 2048)}) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,9 @@ func handleE2EChat(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
// no-transform : interdit à Cloudflare (proxy orange sur ajean.link) de
|
||||
// bufferiser/compresser le flux — sinon les événements arrivent en retard.
|
||||
w.Header().Set("Cache-Control", "no-cache, no-transform")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
flusher, _ := w.(http.Flusher)
|
||||
mu, stop := sseHeartbeat(w, flusher)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Version = "0.4.2"
|
||||
const Version = "0.4.3"
|
||||
|
||||
func main() {
|
||||
// Migration one-shot des anciens skills (SKILLS/<nom>/SKILL.md) vers la
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+3
-3
@@ -3,13 +3,13 @@
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 4,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 4,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
@@ -25,7 +25,7 @@
|
||||
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
||||
"OriginalFilename": "jean.exe",
|
||||
"ProductName": "Jean",
|
||||
"ProductVersion": "0.4.2",
|
||||
"ProductVersion": "0.4.3",
|
||||
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
||||
@@ -830,7 +830,9 @@ func sseHeartbeat(w http.ResponseWriter, flusher http.Flusher) (*sync.Mutex, fun
|
||||
mu := &sync.Mutex{}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
t := time.NewTicker(15 * time.Second)
|
||||
// 4 s (et non 15) : borne le temps qu'un dernier bout de flux peut rester
|
||||
// coincé dans un buffer proxy (Cloudflare) faute d'octets pour le pousser.
|
||||
t := time.NewTicker(4 * time.Second)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
@@ -907,7 +909,7 @@ func handleChat(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-transform")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
flusher, _ := w.(http.Flusher)
|
||||
mu, stop := sseHeartbeat(w, flusher)
|
||||
|
||||
Reference in new issue
Block a user