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
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
rust
use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant};
Building a counting semaphore in Rust
concurrency
synchronization
condition-variable
Advanced
9 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.