go
59 lines · 8 steps
Server-Sent Events streaming in Go
An HTTP handler that keeps a connection open and pushes events to the browser using the SSE protocol.
Explained by
highlit
1package sse
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "time"
8)
9
10type Event struct {
11 ID string
12 Data any
13}
14
15type Broker struct {
16 Subscribe func() (<-chan Event, func())
17}
18
19func (b *Broker) StreamHandler(w http.ResponseWriter, r *http.Request) {
20 flusher, ok := w.(http.Flusher)
21 if !ok {
22 http.Error(w, "streaming unsupported", http.StatusInternalServerError)
23 return
24 }
25
26 w.Header().Set("Content-Type", "text/event-stream")
27 w.Header().Set("Cache-Control", "no-cache")
28 w.Header().Set("Connection", "keep-alive")
29 w.Header().Set("X-Accel-Buffering", "no")
30
31 events, unsubscribe := b.Subscribe()
32 defer unsubscribe()
33
34 keepalive := time.NewTicker(15 * time.Second)
35 defer keepalive.Stop()
36
37 for {
38 select {
39 case <-r.Context().Done():
40 return
41 case <-keepalive.C:
42 fmt.Fprint(w, ": ping\n\n")
43 flusher.Flush()
44 case ev, open := <-events:
45 if !open {
46 return
47 }
48 payload, err := json.Marshal(ev.Data)
49 if err != nil {
50 continue
51 }
52 if ev.ID != "" {
53 fmt.Fprintf(w, "id: %s\n", ev.ID)
54 }
55 fmt.Fprintf(w, "data: %s\n\n", payload)
56 flusher.Flush()
57 }
58 }
59}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1SSE works over a long-lived HTTP response by writing text frames and flushing after each one.
- 2A select over the request context, a keepalive ticker, and an event channel lets one loop handle disconnect, heartbeat, and delivery.
- 3Injecting Subscribe as a function keeps the handler decoupled from how events are actually produced.
Related explainers
rust
use axum::{extract::{Path, State}, http::StatusCode, Json}; use dashmap::DashMap; use serde::Serialize; use std::sync::Arc;
Request coalescing in an Axum handler
caching
concurrency
request-coalescing
Advanced
8 steps
go
package handlers import ( "net/http"
Serving embedded static meta files in Gin
embedding
static assets
http caching
Intermediate
6 steps
go
package config import ( "fmt"
A thread-safe config singleton in Go
singleton
concurrency
environment-variables
Intermediate
7 steps
go
package scheduler import ( "container/heap"
A priority job queue with Go's container/heap
priority-queue
heap
interfaces
Intermediate
9 steps
go
func UploadChunk(c *gin.Context) { uploadID := c.Param("uploadID") if !validUploadID.MatchString(uploadID) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid upload id"})
Resumable chunked uploads in Gin
file-upload
content-range
streaming
Advanced
9 steps
go
func (h *ArticleHandler) Create(c *gin.Context) { var req struct { Title string `json:"title" binding:"required"` Body string `json:"body" binding:"required"`
Handling a POST request in Gin
request binding
validation
http status codes
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/server-sent-events-streaming-in-go-explained-go-6b5d/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.