Controllers
Server-Sent Events
Stream long-lived Server-Sent Events from controller actions.
Start a stream
Use SSEStream from a controller action:
func (c *NotificationsController) Events() error {
stream, err := c.SSEStream()
if err != nil {
return err
}
defer stream.Close()
return stream.Send(lazysse.Event{
Event: "ready",
Data: []string{"connected"},
})
}
Starting the stream commits the response headers and skips automatic view rendering.
Send events
lazysse.Event maps directly to SSE fields:
err := stream.Send(lazysse.Event{
Event: "message",
ID: "123",
Data: []string{"first line", "second line"},
Comment: []string{"optional comment"},
Retry: 5 * time.Second,
})
Each Data entry becomes a data: line. Entries containing newlines are split
into multiple data lines.
Keep the stream alive
Heartbeats keep intermediaries from closing idle connections:
stream.Heartbeat(10 * time.Second)
Long-running actions should exit when the client disconnects:
for {
select {
case <-stream.Done():
return nil
case event := <-events:
if err := stream.Send(event.SSEEvent()); err != nil {
return err
}
}
}
Replay missed events
Browsers reconnect with the Last-Event-ID header. Read it before subscribing
to live events:
if lastID, ok := stream.LastEventID(); ok {
for _, event := range c.Notifications.Since(lastID) {
if err := stream.Send(event.SSEEvent()); err != nil {
return err
}
}
}
SSE streams bypass dynamic route ETags because the response body is written incrementally.