Documentation
¶
Overview ¶
Package sse provides experimental server-sent event helpers for streaming rendered partials.
Index ¶
- Constants
- Variables
- func Attrs(url string, pairs ...any) template.HTMLAttr
- func ClientScript() []byte
- func FuncMap() template.FuncMap
- type Beacon
- type BeaconReason
- type Broker
- type BrokerOptions
- type ClientStream
- type EventName
- type HeaderKey
- type HeaderValue
- type Message
- type Patch
- type ServeOptions
- type Signal
- type Subscription
- type Writer
- func (s *Writer) ApplyHeaders()
- func (s *Writer) Comment(comment string) error
- func (s *Writer) Error(err error) error
- func (s *Writer) Event(event EventName, data any) error
- func (s *Writer) EventID(id string, event EventName, data any) error
- func (s *Writer) Flush()
- func (s *Writer) Heartbeat() error
- func (s *Writer) PatchHTML(target string, html template.HTML) error
- func (s *Writer) PatchPartial(ctx context.Context, r *http.Request, target string, p *partial.Partial) error
- func (s *Writer) Retry(after time.Duration) error
- func (s *Writer) Serve(ctx context.Context, subscription *Subscription, options ServeOptions) error
- func (s *Writer) SetParent(parent *partial.Partial) *Writer
- func (s *Writer) Signal(name string, value any) error
- func (s *Writer) Use(stages ...partial.RenderStage) *Writer
Constants ¶
const ClientName = "partial-sse.js"
ClientName is the name of the browser client in the file system of go-partial's clients package.
Variables ¶
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") )
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
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.
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
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
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 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 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 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) Heartbeat ¶ added in v0.15.0
Heartbeat writes a comment line, which clients ignore, to keep an idle stream open.
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) 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
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.