Package sse

import "funnyhaha.technology/whatwg/sse"
View the source code

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 << 20
source

DefaultMaxEventSize is the largest event a Decoder reads by default.

const MediaType = "text/event-stream"
source

MediaType is the media type of an event stream (Section 9.2.5).

Variables

var ErrEventTooLarge = errors.New("event too large")
source

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")
source

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).

Example

ExampleDecoder shows a client reading the events of a stream, with the last event ID carrying over to an event that has none.

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)
	}
}

Output:

1 first
1 second

NewDecoder source

func NewDecoder(r io.Reader) *Decoder

NewDecoder 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:

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) *Decoder

WithMaxEventSize 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.

Example

ExampleEncoder shows an event with multi-line data and the keep-alive comment a server sends while the stream is idle.

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("")
}

Output:

id: 1
event: tick
data: hello
data: world

:

NewEncoder source

func NewEncoder(w io.Writer) *Encoder

NewEncoder 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) error

Comment 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) error

Encode 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.