sse

package
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package sse provides experimental server-sent event helpers for streaming rendered partials.

Index

Constants

View Source
const ClientName = "partial-sse.js"

ClientName is the name of the browser client in the file system of go-partial's clients package.

Variables

View Source
var (
	// ErrTooManySubscribers is returned by Subscribe when a limit of
	// BrokerOptions is reached.
	ErrTooManySubscribers = errors.New("sse: too many subscribers")
	// ErrBrokerClosed is returned by Subscribe after Close.
	ErrBrokerClosed = errors.New("sse: broker closed")
)
View Source
var ErrInvalidBeacon = errors.New("sse: invalid beacon")

ErrInvalidBeacon is the error of ReadBeacon for a request that is not a report of the browser client.

Functions

func Attrs added in v0.15.0

func Attrs(url string, pairs ...any) template.HTMLAttr

Attrs returns the attributes of an element of the template's own whose stream the browser client of this package (ClientScript) opens, the stream URL followed by key/value pairs of ClientStream's settings: "clientId", "hidden", "beat" (durations such as "30s" or a time.Duration), "beacon" and "credentials":

<div{{ sseAttrs .Stream "clientId" "tab" "hidden" "30s" "beat" "15s" "beacon" "/presence" }}>

An empty URL writes nothing, so a page without a stream needs no condition; invalid settings write a data-partial-error attribute with the error. Register it with FuncMap as "sseAttrs".

go-doc:sig func(url string, pairs ...any) html/template.HTMLAttr

func ClientScript added in v0.15.0

func ClientScript() []byte

ClientScript returns the browser client for the writer's events. It applies partial:patch events to the page, replacing the elements each patch's target selects with its HTML (outerHTML), and dispatches partial:signal (also as partial:signal:<name>) and partial:error as DOM events on the document. Elements with a data-partial-stream="/url" attribute, or the stream markup of interactions' {{ stream }} with connectors that leave the client to the page, open a stream while they are in the page, and a new one when their URL changes; scripts can call goPartialSSE.connect(url). With htmx loaded, patched content is processed by htmx.

An element with data-partial-stream-hidden="30s" closes its stream when the browser tab has been hidden that long, dispatching partial:stream:paused, and opens it again when the tab shows, dispatching partial:stream:reconnect on the element, so the page can fetch what it missed. A page restored from the browser's back/forward cache opens its streams again the same way.

An element with data-partial-stream-client-id="tab" adds a client id to its stream URL as that parameter, the same for the life of the page in the tab, so the server tells the tabs of one browser apart, such as for presence; scripts read it as goPartialSSE.clientId.

An element with data-partial-stream-beacon="/url" reports its stream to that address, which ReadBeacon reads: every data-partial-stream-beat (BeaconBeat), also while the tab is hidden and the stream closed, when the stream closes as the tab is hidden (BeaconHidden), when the element leaves the page and no element left reports to the same address (BeaconRemoved), and when the page is left (BeaconPagehide). Templates write these attributes with {{ sseAttrs }} (see Attrs and ClientStream).

The client inserts the HTML of an event as the page's own: under a Content-Security-Policy without 'unsafe-inline', its style attributes are blocked, and an inline <style> or <script> in it applies only when the page's policy has its hash. Style patched content with classes.

The client needs no build step and no other library; serve it from the file system of go-partial's clients package, or include it in your own bundle.

func FuncMap added in v0.15.0

func FuncMap() template.FuncMap

FuncMap returns the template helpers of the browser client: {{ sseAttrs }} (see Attrs).

Types

type Beacon added in v0.15.0

type Beacon struct {
	// ClientID is the id of the browser tab, goPartialSSE.clientId.
	ClientID string
	// Params are the parameters of the stream URL, with the client id
	// under the name of ClientStream.ClientID, such as tab, and those
	// of the application's own, such as the page of a presence stream.
	Params url.Values
	// Reason says why the client reports.
	Reason BeaconReason
}

Beacon is a report of the browser client (ClientScript) about a stream of an element with data-partial-stream-beacon (see ClientStream.Beacon); ReadBeacon reads it.

func ReadBeacon added in v0.15.0

func ReadBeacon(r *http.Request) (Beacon, error)

ReadBeacon reads the report of the browser client from r, a POST to the address of data-partial-stream-beacon: the form fields clientId, reason and params, the query of the stream URL. It reads at most 16 KiB and returns an error wrapping ErrInvalidBeacon for a request without a client id or with an unknown reason.

beacon, err := sse.ReadBeacon(r)
if err != nil {
	http.Error(w, "invalid_beacon", http.StatusBadRequest)
	return
}
if beacon.Reason.Leaving() {
	presence.Leave(beacon.Params.Get("tab"))
} else {
	presence.Visit(beacon.Params.Get("tab"), beacon.Params.Get("page"))
}

type BeaconReason added in v0.15.0

type BeaconReason string

BeaconReason says why the browser client reports a stream.

const (
	// BeaconBeat is a report every ClientStream.Beat, while the element is
	// in the page, also while the tab is hidden and the stream closed.
	BeaconBeat BeaconReason = "beat"
	// BeaconHidden says the stream closed as the tab is hidden (see
	// ClientStream.Hidden); the tab is still there.
	BeaconHidden BeaconReason = "hidden"
	// BeaconRemoved says the element left the page, such as after a
	// navigation to a page without the stream, and no element in the page
	// reports to the same address.
	BeaconRemoved BeaconReason = "removed"
	// BeaconPagehide says the page is being left, or kept in the browser's
	// back/forward cache, which opens the stream again when it is restored.
	BeaconPagehide BeaconReason = "pagehide"
)

func (BeaconReason) Leaving added in v0.15.0

func (r BeaconReason) Leaving() bool

Leaving reports whether the reason says the tab no longer shows the stream's element: BeaconRemoved or BeaconPagehide.

type Broker added in v0.15.0

type Broker struct {
	// contains filtered or unexported fields
}

Broker fans messages out to subscriptions by topic, for streams that many clients follow, such as everyone looking at the same page. It keeps its state in memory: it serves the streams of one process.

A Broker is safe for concurrent use.

func NewBroker added in v0.15.0

func NewBroker(options BrokerOptions) *Broker

NewBroker returns a broker with the given limits.

func (*Broker) Close added in v0.15.0

func (b *Broker) Close()

Close ends every subscription, so their streams finish, for example when the server shuts down. Later calls to Subscribe fail with ErrBrokerClosed.

func (*Broker) Publish added in v0.15.0

func (b *Broker) Publish(topic string, message Message) int

Publish sends message to every subscription of topic and reports how many received it. The message's Topic is set to topic. Publish never blocks: a subscription whose buffer is full misses the message.

func (*Broker) Subscribe added in v0.15.0

func (b *Broker) Subscribe(key string, topics ...string) (*Subscription, error)

Subscribe opens a subscription to topics. key groups subscriptions for BrokerOptions.MaxPerKey, for example a user ID; an empty key is not limited per key. Close the subscription when the stream ends.

type BrokerOptions added in v0.15.0

type BrokerOptions struct {
	// MaxSubscribers caps the open subscriptions; zero is unlimited.
	MaxSubscribers int
	// MaxPerKey caps the open subscriptions that share a key, such as the
	// streams of one user; zero is unlimited.
	MaxPerKey int
	// Buffer is the number of messages a subscription holds while its
	// stream is busy; zero means 16. Messages for a full subscription are
	// dropped, so a slow client never blocks Publish. A buffer of 1 coalesces "something changed" notifications:
	// the subscriber sees one, however many were published meanwhile.
	Buffer int
}

BrokerOptions limit a Broker.

type ClientStream added in v0.15.0

type ClientStream struct {
	// URL is the address of the stream: data-partial-stream. Without it,
	// Attrs writes nothing.
	URL string
	// ClientID names the parameter of the stream URL that the client
	// adds the id of the browser tab to, such as "tab":
	// data-partial-stream-client-id.
	ClientID string
	// Hidden closes the stream once the tab has been hidden that long,
	// and opens it again when the tab shows:
	// data-partial-stream-hidden.
	Hidden time.Duration
	// Beat reports the stream to Beacon at this interval, while the
	// element is in the page, also while the tab is hidden and the
	// stream closed: data-partial-stream-beat.
	Beat time.Duration
	// Beacon is the address the client reports the stream to, with its
	// beats and when it is hidden, removed or the page is left, which
	// ReadBeacon reads: data-partial-stream-beacon.
	Beacon string
	// Credentials sends cookies with a stream of another origin:
	// data-partial-credentials.
	Credentials bool
}

ClientStream is an element whose stream the browser client of this package (ClientScript) opens, as attributes; see Attrs. Templates write the same attributes with {{ sseAttrs }}.

stream := sse.ClientStream{
	URL:      "/presence?page=/orders",
	ClientID: "tab",
	Hidden:   30 * time.Second,
	Beat:     15 * time.Second,
	Beacon:   "/presence",
}
attrs := stream.Attrs()

func (ClientStream) Attrs added in v0.15.0

func (s ClientStream) Attrs() template.HTMLAttr

Attrs returns the attributes of the stream's element, each after a space, or nothing without a URL.

type EventName

type EventName string
const (
	EventPatch  EventName = "partial:patch"
	EventSignal EventName = "partial:signal"
	EventError  EventName = "partial:error"
)

func (EventName) String

func (e EventName) String() string

type HeaderKey

type HeaderKey string
const (
	HeaderContentType     HeaderKey = "Content-Type"
	HeaderCacheControl    HeaderKey = "Cache-Control"
	HeaderConnection      HeaderKey = "Connection"
	HeaderXAccelBuffering HeaderKey = "X-Accel-Buffering"
)

func (HeaderKey) String

func (h HeaderKey) String() string

type HeaderValue

type HeaderValue string
const (
	ContentTypeEventStream HeaderValue = "text/event-stream"
	CacheControlNoCache    HeaderValue = "no-cache"
	ConnectionKeepAlive    HeaderValue = "keep-alive"
	XAccelBufferingNo      HeaderValue = "no"
)

func (HeaderValue) String

func (v HeaderValue) String() string

type Message added in v0.15.0

type Message struct {
	// Topic is where the message was published.
	Topic string
	// ID, Event and Data are written as the event's fields by Serve when no
	// handler takes the message.
	ID    string
	Event EventName
	Data  any
}

Message is one event published to the subscribers of a topic.

type Patch

type Patch struct {
	Target string        `json:"target"`
	HTML   template.HTML `json:"html"`
}

Patch is the data of a partial:patch event: HTML for the elements that Target selects.

type ServeOptions added in v0.15.0

type ServeOptions struct {
	// Heartbeat is the interval at which Serve writes a comment, which keeps
	// proxies from closing an idle connection and lets the server notice a
	// client that went away. Zero sends none.
	Heartbeat time.Duration
	// BeforeHeartbeat runs before every heartbeat; an error ends Serve with
	// that error, for example when the user's session ended meanwhile.
	BeforeHeartbeat func() error
	// Handle writes a message to the stream, for example a partial rendered
	// for this client. Nil writes the message's ID, Event and Data.
	Handle func(w *Writer, message Message) error
}

ServeOptions configure Writer.Serve.

type Signal

type Signal struct {
	Name  string `json:"name"`
	Value any    `json:"value"`
}

type Subscription added in v0.15.0

type Subscription struct {
	// contains filtered or unexported fields
}

Subscription receives the messages of its topics.

func (*Subscription) Close added in v0.15.0

func (s *Subscription) Close()

Close ends the subscription and frees its place in the broker's limits. It is safe to call more than once.

func (*Subscription) Messages added in v0.15.0

func (s *Subscription) Messages() <-chan Message

Messages is the subscription's queue; it is closed when the subscription or the broker closes.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer streams server-sent events to one http.ResponseWriter.

Writer is bound to a single response stream and is not safe for concurrent writes unless the caller serializes access.

func NewWriter

func NewWriter(w http.ResponseWriter) *Writer

func (*Writer) ApplyHeaders

func (s *Writer) ApplyHeaders()

func (*Writer) Comment

func (s *Writer) Comment(comment string) error

func (*Writer) Error

func (s *Writer) Error(err error) error

func (*Writer) Event

func (s *Writer) Event(event EventName, data any) error

func (*Writer) EventID

func (s *Writer) EventID(id string, event EventName, data any) error

func (*Writer) Flush

func (s *Writer) Flush()

func (*Writer) Heartbeat added in v0.15.0

func (s *Writer) Heartbeat() error

Heartbeat writes a comment line, which clients ignore, to keep an idle stream open.

func (*Writer) PatchHTML

func (s *Writer) PatchHTML(target string, html template.HTML) error

func (*Writer) PatchPartial

func (s *Writer) PatchPartial(ctx context.Context, r *http.Request, target string, p *partial.Partial) error

PatchPartial renders p itself (see partial.RenderSelf), with the writer's stages and under its parent (see Use and SetParent), and writes it as a partial:patch event for the elements target selects. p is not changed. A nil ctx is the request's context.

Under an application's root partial, a part of a page streams with the root's functions, stages, file system and @model values, such as a list of the viewers of a page, without a render helper of its own:

events := sse.NewWriter(w).SetParent(root)
viewers := partial.NewID("viewers", "viewers.gohtml").SetDot(view)
err := events.PatchPartial(r.Context(), r, "#viewers", viewers)

func (*Writer) Retry

func (s *Writer) Retry(after time.Duration) error

func (*Writer) Serve added in v0.15.0

func (s *Writer) Serve(ctx context.Context, subscription *Subscription, options ServeOptions) error

Serve streams the subscription's messages until ctx ends, the subscription closes or a write fails, flushing after every event. It returns nil when the subscription closed and the error that ended it otherwise; ctx's error when the client went away.

Serve clears the connection's write deadline, as an http.Server's WriteTimeout would otherwise end every stream; heartbeats keep detecting clients that disappeared.

func (*Writer) SetParent added in v0.15.0

func (s *Writer) SetParent(parent *partial.Partial) *Writer

SetParent renders the partials PatchPartial patches under parent, as partial.Partial.SetParent does, unless they have a parent of their own: with its functions, render stages, connector, file system, template cache, failure policy and @model values, such as those of an application's configured root partial, without adding them to it.

func (*Writer) Signal

func (s *Writer) Signal(name string, value any) error

func (*Writer) Use

func (s *Writer) Use(stages ...partial.RenderStage) *Writer

Use appends render stages to partials rendered by this writer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL