Package sse
Overview
Package sse writes and reads the event stream format of server-sent events, defined by the HTML Living Standard, Section 9.2: the text/event-stream body that a browser's EventSource reads.
A server writes events with an Encoder, flushing after each one so the client receives it at once:
w.Header().Set("Content-Type", sse.MediaType)
enc := sse.NewEncoder(w)
err := enc.Encode(sse.Event{ID: "1", Data: "hello"})
http.NewResponseController(w).Flush()A client reads them with a Decoder, which interprets the stream as Section 9.2.6 describes for a browser.
The package covers the stream format (Section 9.2.5) and its interpretation (Section 9.2.6). It doesn't cover the EventSource API, reconnection, or the HTTP exchange itself.
The text of Section 9.2 is in server-sent-events.html in this directory, fetched from https://html.spec.whatwg.org/multipage/server-sent-events.html as of whatwg/html commit 790ce89a0b95f3f6caed7fc2d1e3f352364e2791 (14 October 2026). The doc comments cite its sections. It's copyright WHATWG (Apple, Google, Mozilla, Microsoft), licensed under CC BY 4.0.
Index
Constants
const DefaultMaxEventSize = 1 << 20DefaultMaxEventSize is the largest event a Decoder reads by default.
const MediaType = "text/event-stream"MediaType is the media type of an event stream (Section 9.2.5).
Variables
var ErrEventTooLarge = errors.New("event too large")ErrEventTooLarge is returned by Decoder.Next for an event, or a line, longer than the decoder's limit.
var ErrInvalidField = errors.New("invalid event field")ErrInvalidField is returned by Encoder.Encode for an event that can't be written: an ID or type containing a line break, an ID containing NULL (which a client would ignore), or a field that isn't valid UTF-8.
Types
Decoder source
type Decoder struct {
// contains filtered or unexported fields
}Decoder reads events from a stream, interpreting it as a browser does
(Section 9.2.6). ExampleDecoder shows a client reading the events of a stream, with the
last event ID carrying over to an event that has none. Output:Example
package main
import (
"fmt"
"strings"
"funnyhaha.technology/whatwg/sse"
)
func main() {
dec := sse.NewDecoder(strings.NewReader("id: 1\ndata: first\n\ndata: second\n\n"))
for {
ev, err := dec.Next()
if err != nil {
break
}
fmt.Printf("%s %s\n", ev.ID, ev.Data)
}
}
1 first
1 second
NewDecoder source
func NewDecoder(r io.Reader) *DecoderNewDecoder returns a Decoder that reads from r, with a limit of DefaultMaxEventSize per event.
(Decoder) Next source
func (d *Decoder) Next() (Event, error)Next returns the next event that a browser would dispatch. Following Section 9.2.6:
- Blocks with no data field aren't dispatched, so Next skips them, though their id and retry fields still take effect. A data field with an empty value does dispatch an event, with empty Data.
- The ID is the last event ID and Retry the reconnection time, or zero if no retry field has set it. Each is stream state rather than part of one event: once set, it carries over to later events until another field changes it.
- Type is empty for the default type, "message".
Next returns io.EOF at the end of the stream; an event without its final blank line is discarded.
(Decoder) WithMaxEventSize source
func (d *Decoder) WithMaxEventSize(n int) *DecoderWithMaxEventSize sets the largest event, in bytes, that Next reads before returning ErrEventTooLarge. It returns d.
Encoder source
type Encoder struct {
// contains filtered or unexported fields
}Encoder writes events to a stream. ExampleEncoder shows an event with multi-line data and the keep-alive
comment a server sends while the stream is idle. Output:Example
package main
import (
"os"
"funnyhaha.technology/whatwg/sse"
)
func main() {
enc := sse.NewEncoder(os.Stdout)
_ = enc.Encode(sse.Event{ID: "1", Type: "tick", Data: "hello\nworld"})
_ = enc.Comment("")
}
id: 1
event: tick
data: hello
data: world
:
NewEncoder source
func NewEncoder(w io.Writer) *EncoderNewEncoder returns an Encoder that writes to w. It doesn't buffer, so each event is passed to w in a single Write; flush w after it if w buffers.
(Encoder) Comment source
func (e *Encoder) Comment(text string) errorComment writes a comment, which the client ignores. An empty comment is a cheap keep-alive: Section 9.2.7 suggests one every 15 seconds or so, for proxies that drop idle connections. A comment with line breaks is written as several comment lines.
(Encoder) Encode source
func (e *Encoder) Encode(ev Event) errorEncode writes e, followed by the blank line that dispatches it. Data is written as one "data:" line per line of Data, with a space after the colon so a leading space in the data is kept (Section 9.2.6).
An event with empty Data is written with no data field, so a browser doesn't dispatch it (Section 9.2.6), but it can still set the ID or Retry.
Event source
type Event struct {
// ID sets the client's last event ID, which it sends back in the
// Last-Event-ID header when it reconnects. Empty means no id field.
ID string
// Type is the event type. Empty means "message".
Type string
// Data is the event's data. It may contain line breaks: CRLF and CR
// are read back as LF.
Data string
// Retry sets the client's reconnection time, in whole milliseconds.
// Zero means no retry field. Like ID, it lasts until changed, so a
// Decoder reports it on every later event.
Retry time.Duration
}Event is one server-sent event.
See HTML Living Standard, Section 9.2.6.