44 "fmt"
55 "log"
66 "net/http"
7- "time"
87
98 "github.com/launchdarkly/go-server-sdk/v7/subsystems"
109 "github.com/launchdarkly/ldcli/internal/dev_server/model"
@@ -28,72 +27,41 @@ func StreamV2(w http.ResponseWriter, r *http.Request) {
2827 return
2928 }
3029
31- flusher , ok := w .(http.Flusher )
32- if ! ok {
33- WriteError (ctx , w , errors .New ("streaming not supported" ))
34- return
35- }
36-
3730 initialPayload , err := buildFullTransferResponse (projectKey , project .PayloadVersion , allFlags , fdv2ReasonPayloadMissing )
3831 if err != nil {
3932 WriteError (ctx , w , errors .Wrap (err , "failed to build initial payload" ))
4033 return
4134 }
4235
43- // Register observer before writing to the client so that any changes arriving
44- // during the initial write are queued and delivered immediately after.
45- updateChan := make (chan []subsystems.RawEvent , 10 )
46- observerID := model .GetObserversFromContext (ctx ).RegisterObserver (fdv2StreamObserver {
47- updateChan : updateChan ,
48- projectKey : projectKey ,
49- })
36+ updateChan , doneChan := OpenStream (w , r .Context ().Done (), fdv2SSEPayload (initialPayload .Events ))
37+ defer close (updateChan )
38+
39+ observer := fdv2StreamObserver {updateChan : updateChan , projectKey : projectKey }
40+ observerID := model .GetObserversFromContext (ctx ).RegisterObserver (observer )
5041 defer func () {
5142 if ok := model .GetObserversFromContext (ctx ).DeregisterObserver (observerID ); ! ok {
5243 log .Printf ("unable to deregister fdv2 stream observer" )
5344 }
5445 }()
5546
56- w .Header ().Set ("Content-Type" , "text/event-stream" )
57- w .Header ().Set ("Cache-Control" , "no-cache" )
58-
59- if err := writeFDv2SSEEvents (w , flusher , initialPayload .Events ); err != nil {
60- return
61- }
62-
63- ticker := time .NewTicker (time .Minute )
64- defer ticker .Stop ()
65-
66- for {
67- select {
68- case events := <- updateChan :
69- if err := writeFDv2SSEEvents (w , flusher , events ); err != nil {
70- return
71- }
72- case <- ticker .C :
73- // SSE comment line as a keepalive.
74- if _ , err := w .Write ([]byte (":\n \n " )); err != nil {
75- return
76- }
77- flusher .Flush ()
78- case <- ctx .Done ():
79- return
80- }
47+ err = <- doneChan
48+ if err != nil {
49+ WriteError (ctx , w , errors .Wrap (err , "stream failure" ))
8150 }
8251}
8352
84- // writeFDv2SSEEvents writes a batch of FDv2 events to the response as individual SSE events .
85- func writeFDv2SSEEvents ( w http. ResponseWriter , flusher http. Flusher , events []subsystems. RawEvent ) error {
86- for _ , event := range events {
87- if _ , err := fmt . Fprintf ( w , "event:%s \n data:%s \n \n " , event . Name , event . Data ); err != nil {
88- return err
89- }
53+ // fdv2SSEPayload formats a slice of FDv2 events as raw SSE bytes .
54+ // Each event becomes an individual SSE event in the output.
55+ func fdv2SSEPayload ( events []subsystems. RawEvent ) [] byte {
56+ var buf [] byte
57+ for _ , e := range events {
58+ buf = append ( buf , fmt . Sprintf ( "event:%s \n data:%s \n \n " , e . Name , e . Data ) ... )
9059 }
91- flusher .Flush ()
92- return nil
60+ return buf
9361}
9462
9563type fdv2StreamObserver struct {
96- updateChan chan <- []subsystems. RawEvent
64+ updateChan chan <- []byte
9765 projectKey string
9866}
9967
@@ -107,7 +75,7 @@ func (o fdv2StreamObserver) Handle(event interface{}) {
10775 if err != nil {
10876 panic (errors .Wrap (err , "failed to build flag change events in fdv2 stream observer" ))
10977 }
110- o .updateChan <- events
78+ o .updateChan <- fdv2SSEPayload ( events )
11179 case model.SyncEvent :
11280 if event .ProjectKey != o .projectKey {
11381 return
@@ -116,6 +84,6 @@ func (o fdv2StreamObserver) Handle(event interface{}) {
11684 if err != nil {
11785 panic (errors .Wrap (err , "failed to build full transfer in fdv2 stream observer" ))
11886 }
119- o .updateChan <- payload .Events
87+ o .updateChan <- fdv2SSEPayload ( payload .Events )
12088 }
12189}
0 commit comments