返回 DeepSeek-Reasonix
events_http.go
根目录 / internal / serve / events_http.go
1 package serve
2
3 import (
4 "fmt"
5 "net/http"
6 "time"
7
8 "reasonix/internal/event"
9 )
10
11 // Keepalives prevent quiet turns from being closed by common 30–60 s proxies.
12 const sseKeepaliveInterval = 15 * time.Second
13
14 func (s *Server) events(w http.ResponseWriter, r *http.Request) {
15 flusher, ok := w.(http.Flusher)
16 if !ok {
17 http.Error(w, "streaming unsupported", http.StatusInternalServerError)
18 return
19 }
20 w.Header().Set("Content-Type", "text/event-stream")
21 w.Header().Set("Cache-Control", "no-cache")
22 w.Header().Set("Connection", "keep-alive")
23 var ch <-chan []byte
24 var unsubscribe func()
25 // Session switches also hold bindMu. Capture, subscribe, and replay in that
26 // epoch so a promoted controller cannot broadcast its prompt before this
27 // current-only subscriber exists.
28 s.bindMu.Lock()
29 ctrl := s.ctl()
30 currentPath := ctrl.SessionPath()
31 ctrl.ReplayPendingPromptsWith(func() event.Sink {
32 if r.URL.Query().Get("all") == "1" {
33 ch, unsubscribe = s.bc.SubscribeAll()
34 } else {
35 ch, unsubscribe = s.bc.Subscribe()
36 }
37 return event.FuncSink(func(e event.Event) {
38 if currentPath != "" {
39 e.SessionPath = currentPath
40 }
41 s.bc.EmitTo(ch, e)
42 })
43 })
44 s.bindMu.Unlock()
45 defer unsubscribe()
46 fmt.Fprint(w, ": connected\n\n")
47 flusher.Flush()
48 keepalive := time.NewTicker(sseKeepaliveInterval)
49 defer keepalive.Stop()
50 for {
51 select {
52 case data, ok := <-ch:
53 if !ok {
54 return
55 }
56 fmt.Fprintf(w, "data: %s\n\n", data)
57 flusher.Flush()
58 case <-keepalive.C:
59 fmt.Fprint(w, ": ping\n\n")
60 flusher.Flush()
61 case <-r.Context().Done():
62 return
63 }
64 }
65 }
66
66 lines GO