Documentation
¶
Overview ¶
Package sse is the SSE FORMAT binding for essessey: the wire codec plus the byte-stream Sinks (WriterSink, HTTPSink) and the byte-stream Source that frame/parse it.
SSE is a FORMAT, not a transport peer of NATS/WebSocket. Its framing exists because an HTTP response body is an undelimited byte stream and something must mark where one event ends — message-oriented transports need none. So this package owns ALL framing; other bindings own none.
The wire format implemented here is the one defined by the WHATWG HTML Living Standard under "Server-sent events", including the parts a single-consumer implementation is tempted to skip: multi-line payloads, the optional space after a field's colon, all three line terminators, comment lines, and the empty-data rule.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotAFlusher = errors.New("response writer is not an http.Flusher")
ErrNotAFlusher means the http.ResponseWriter cannot flush.
Refused at construction rather than tolerated: a non-flushing writer buffers the whole response, so the stream would arrive as one lump at the end. That looks like a slow model rather than a wiring bug, which is exactly the kind of failure that survives to production.
Functions ¶
func FrameComment ¶ added in v0.5.0
FrameComment renders a comment line, which a conformant receiver ignores.
This is the format's keep-alive: an idle connection can be dropped by an intermediary that sees no bytes, and a comment is the cheapest traffic that prevents it without the receiver needing to understand anything.
func FrameLines ¶
FrameLines renders the canonical wire bytes for ev: the optional `id:` line, the `event:` line, one `data:` line PER LINE of the payload, then a blank line terminator.
The per-line data split is the whole reason this is not a single Sprintf. A payload is arbitrary bytes; the format has no escaping and no length prefix, so a raw newline inside a value ends the FIELD and a raw blank line ends the EVENT. Emitting a multi-line payload as one `data:` line therefore puts something on the wire that differs from what the caller passed — and for a payload containing a blank line it forges an extra event out of the remainder.
func FrameRetry ¶ added in v0.5.0
FrameRetry renders a `retry:` line telling the receiver how long to wait before reconnecting after the stream drops.
It describes the STREAM rather than any one event, which is why it is its own frame instead of a field on Event. The format carries milliseconds and a receiver ignores any value that is not all ASCII digits, so a negative duration is clamped to zero rather than written as something that would be silently discarded.
Types ¶
type HTTPSink ¶
type HTTPSink struct {
// contains filtered or unexported fields
}
HTTPSink frames and writes each event straight to a flushing http.ResponseWriter, flushing after every event so the client sees it immediately instead of buffered behind the transport.
func NewHTTPSink ¶
func NewHTTPSink(w http.ResponseWriter) (*HTTPSink, error)
NewHTTPSink builds an HTTPSink over w. It errors if w does not implement http.Flusher — a non-flushing writer would buffer the whole stream and defeat streaming.
type Source ¶
type Source struct {
// contains filtered or unexported fields
}
Source reads framed SSE bytes off an io.Reader and yields essessey.Event — the read-side mirror of WriterSink/HTTPSink.
This implements the format's parsing algorithm rather than matching pairs of lines, because the two are not the same thing:
- An event ends at a BLANK LINE, not at whatever line comes next. A payload spans as many `data:` fields as it has lines, and they are joined back together with newlines.
- The space after a field's colon is OPTIONAL, so `data:x` and `data: x` carry the same value. Requiring it makes conformant streams from other producers unreadable.
- Lines end with CRLF, LF, or a lone CR.
- An event whose data buffer stayed empty is DISCARDED, not delivered.
- The last-event-ID persists across events until the producer changes it, while the event type and data buffers reset after every dispatch.
func (*Source) LastEventID ¶ added in v0.5.0
LastEventID returns the most recent id the producer sent, which persists across events until replaced.
This is the value a client sends back as the Last-Event-ID request header on reconnect, so a producer can resume rather than restart the stream.
func (*Source) Next ¶
Next returns the next event, or essessey.ErrNoMoreEvents once the underlying reader is exhausted cleanly.
A partial event at end of input is discarded rather than delivered: the producer never terminated it, so its payload may be incomplete, and handing a caller truncated bytes is worse than admitting the stream ended.
type WriterSink ¶
type WriterSink struct {
// contains filtered or unexported fields
}
WriterSink frames and writes each event to an underlying io.Writer, guarding concurrent writes with a mutex — the tool loop can produce blocks from several goroutines.
func NewWriterSink ¶
func NewWriterSink(w io.Writer) *WriterSink
NewWriterSink builds a WriterSink over w.