@@ -23,15 +23,19 @@ import (
2323var openapiSpec string
2424
2525// Server wraps the ChainManager with Fiber handlers
26+ //
27+ //nolint:containedctx // Context stored for SSE stream shutdown detection
2628type Server struct {
29+ ctx context.Context
2730 cm * chaintracks.ChainManager
2831 sseClients map [int64 ]* bufio.Writer
2932 sseClientsMu sync.RWMutex
3033}
3134
3235// NewServer creates a new API server
33- func NewServer (cm * chaintracks.ChainManager ) * Server {
36+ func NewServer (ctx context. Context , cm * chaintracks.ChainManager ) * Server {
3437 return & Server {
38+ ctx : ctx ,
3539 cm : cm ,
3640 sseClients : make (map [int64 ]* bufio.Writer ),
3741 }
@@ -97,6 +101,9 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
97101 c .Set ("Connection" , "keep-alive" )
98102 c .Set ("Transfer-Encoding" , "chunked" )
99103
104+ // Capture context before entering stream writer
105+ ctx := c .UserContext ()
106+
100107 c .Context ().SetBodyStreamWriter (fasthttp .StreamWriter (func (w * bufio.Writer ) {
101108 clientID := time .Now ().UnixNano ()
102109
@@ -111,7 +118,7 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
111118 }()
112119
113120 // Send initial tip
114- tip := s .cm .GetTip ()
121+ tip := s .cm .GetTip (ctx )
115122 if tip != nil { //nolint:nestif // SSE stream initialization logic
116123
117124 data , err := json .Marshal (tip )
@@ -129,15 +136,17 @@ func (s *Server) HandleTipStream(c *fiber.Ctx) error {
129136 ticker := time .NewTicker (15 * time .Second )
130137 defer ticker .Stop ()
131138
132- for range ticker .C {
133- // Send keepalive comment
134- if _ , writeErr := fmt .Fprintf (w , ": keepalive\n \n " ); writeErr != nil {
135- // Connection closed
136- return
137- }
138- if err := w .Flush (); err != nil {
139- // Connection closed
139+ for {
140+ select {
141+ case <- s .ctx .Done ():
140142 return
143+ case <- ticker .C :
144+ if _ , writeErr := fmt .Fprintf (w , ": keepalive\n \n " ); writeErr != nil {
145+ return
146+ }
147+ if err := w .Flush (); err != nil {
148+ return
149+ }
141150 }
142151 }
143152 }))
@@ -169,7 +178,7 @@ func (s *Server) HandleRobots(c *fiber.Ctx) error {
169178
170179// HandleGetNetwork returns the network name
171180func (s * Server ) HandleGetNetwork (c * fiber.Ctx ) error {
172- network , err := s .cm .GetNetwork ()
181+ network , err := s .cm .GetNetwork (c . UserContext () )
173182 if err != nil {
174183 return c .Status (fiber .StatusInternalServerError ).JSON (Response {
175184 Status : "error" ,
@@ -187,15 +196,15 @@ func (s *Server) HandleGetHeight(c *fiber.Ctx) error {
187196 c .Set ("Cache-Control" , "public, max-age=60" )
188197 return c .JSON (Response {
189198 Status : "success" ,
190- Value : s .cm .GetHeight (),
199+ Value : s .cm .GetHeight (c . UserContext () ),
191200 })
192201}
193202
194203// HandleGetTipHash returns the chain tip hash
195204func (s * Server ) HandleGetTipHash (c * fiber.Ctx ) error {
196205 c .Set ("Cache-Control" , "no-cache" )
197206
198- tip := s .cm .GetTip ()
207+ tip := s .cm .GetTip (c . UserContext () )
199208 if tip == nil {
200209 return c .Status (fiber .StatusNotFound ).JSON (Response {
201210 Status : "error" ,
@@ -215,7 +224,7 @@ func (s *Server) HandleGetTipHash(c *fiber.Ctx) error {
215224func (s * Server ) HandleGetTipHeader (c * fiber.Ctx ) error {
216225 c .Set ("Cache-Control" , "no-cache" )
217226
218- tip := s .cm .GetTip ()
227+ tip := s .cm .GetTip (c . UserContext () )
219228 if tip == nil {
220229 return c .Status (fiber .StatusNotFound ).JSON (Response {
221230 Status : "error" ,
@@ -250,18 +259,19 @@ func (s *Server) HandleGetHeaderByHeight(c *fiber.Ctx) error {
250259 })
251260 }
252261
253- tip := s .cm .GetHeight ()
262+ tip := s .cm .GetHeight (c . UserContext () )
254263 if uint32 (height ) < tip - 100 {
255264 c .Set ("Cache-Control" , "public, max-age=3600" )
256265 } else {
257266 c .Set ("Cache-Control" , "no-cache" )
258267 }
259268
260- header , err := s .cm .GetHeaderByHeight (uint32 (height ))
269+ header , err := s .cm .GetHeaderByHeight (c . UserContext (), uint32 (height ))
261270 if err != nil {
262- return c .JSON (Response {
263- Status : "success" ,
264- Value : nil ,
271+ return c .Status (fiber .StatusNotFound ).JSON (Response {
272+ Status : "error" ,
273+ Code : "ERR_NOT_FOUND" ,
274+ Description : "Header not found at height " + heightStr ,
265275 })
266276 }
267277
@@ -291,15 +301,16 @@ func (s *Server) HandleGetHeaderByHash(c *fiber.Ctx) error {
291301 })
292302 }
293303
294- header , err := s .cm .GetHeaderByHash (hash )
304+ header , err := s .cm .GetHeaderByHash (c . UserContext (), hash )
295305 if err != nil {
296- return c .JSON (Response {
297- Status : "success" ,
298- Value : nil ,
306+ return c .Status (fiber .StatusNotFound ).JSON (Response {
307+ Status : "error" ,
308+ Code : "ERR_NOT_FOUND" ,
309+ Description : "Header not found for hash " + hashStr ,
299310 })
300311 }
301312
302- tip := s .cm .GetHeight ()
313+ tip := s .cm .GetHeight (c . UserContext () )
303314 if header .Height < tip - 100 {
304315 c .Set ("Cache-Control" , "public, max-age=3600" )
305316 } else {
@@ -343,7 +354,7 @@ func (s *Server) HandleGetHeaders(c *fiber.Ctx) error {
343354 })
344355 }
345356
346- tip := s .cm .GetHeight ()
357+ tip := s .cm .GetHeight (c . UserContext () )
347358 if uint32 (height ) < tip - 100 {
348359 c .Set ("Cache-Control" , "public, max-age=3600" )
349360 } else {
@@ -353,7 +364,7 @@ func (s *Server) HandleGetHeaders(c *fiber.Ctx) error {
353364 var hexData string
354365 for i := uint32 (0 ); i < uint32 (count ); i ++ {
355366 h := uint32 (height ) + i
356- header , err := s .cm .GetHeaderByHeight (h )
367+ header , err := s .cm .GetHeaderByHeight (c . UserContext (), h )
357368 if err != nil {
358369 break
359370 }
0 commit comments