Documentation
¶
Overview ¶
Package realtime serves subscriptions to document changes as server-sent events (design/triggers-and-realtime.md §17 and §20). It is driver-neutral and knows nothing of HTTP: the Hub owns the limits, buffers and the shared change feed; a Stream is one open connection with its subscriptions; the httpapi package turns requests into streams and frames into bytes.
Index ¶
- Constants
- Variables
- func CollKey(db, coll string) string
- func Seal(key []byte, t Token) string
- type Access
- type Frame
- type Hub
- func (h *Hub) Buffered() int64
- func (h *Hub) Change(_ context.Context, c changefeed.Change, at changefeed.Position)
- func (h *Hub) ConfigChanged(colls map[string]bool)
- func (h *Hub) Gap(context.Context, changefeed.Gap)
- func (h *Hub) Idle(context.Context, changefeed.Position)
- func (h *Hub) Open(req Request) (*Stream, error)
- func (h *Hub) Streams() int
- type Key
- type Limits
- type Observer
- type Reloaded
- type Request
- type Stream
- type Subscription
- type Token
- type Visibility
Constants ¶
const ( EventSessionEnded = "session_ended" // the session, user or key is gone: sign in again EventReauth = "reauth" // the credential's lifetime is over: reconnect with a fresh one )
Events that end a stream (design §17.2).
const ( ReasonHistoryExpired = "history_expired" // the change history no longer reaches the stream ReasonAccessChanged = "access_changed" // the subscriber's access changed (design §19) ReasonSlowConsumer = "slow_consumer" // the stream's buffer was dropped ReasonPreimageMiss = "preimage_missing" ReasonConfigChanged = "config_changed" // the configuration the subscription depended on changed: subscribe again )
Reset reasons: what a `reset` event says about why a client has to reload.
Variables ¶
var ( ErrTooManyStreams = errors.New("realtime: this instance has too many open streams") ErrUserStreams = errors.New("realtime: too many open streams for this caller") ErrOpenTooFast = errors.New("realtime: streams are opened too fast") )
Refusals when a stream can't be opened. Each says when to try again.
var DefaultLimits = Limits{ SubscriptionsPerStream: 20, StreamsPerUser: 5, Streams: 10000, BufferBytes: 256 << 10, BufferEvents: 1000, Memory: 256 << 20, Heartbeat: 25 * time.Second, OpenInterval: time.Second, ResumeMaxAge: 24 * time.Hour, }
DefaultLimits are used for what is zero.
var ErrBadToken = errors.New("realtime: invalid resume token")
ErrBadToken means a resume token is malformed, was not signed with this realm's key, or belongs to another realm or principal.
var ErrTooManySubscriptions = errors.New("realtime: too many subscriptions")
ErrTooManySubscriptions is returned when a request has more subscriptions than allowed.
var Heartbeat = Frame{}
Heartbeat is the frame that keeps an idle connection alive.
Functions ¶
Types ¶
type Access ¶
type Access struct {
SessionID string // the session's id (its document key)
UserID string
KeyID string // an API key's document key (its hash)
Orgs []string
// Fingerprint summarizes what the caller's access depends on (roles, memberships, second
// factor, consents). Resume tokens carry it: replaying history made while the access was
// different would show states the caller couldn't see then.
Fingerprint string
// UsesConfig says a subscribed collection's rules read the app configuration, so a change of
// the configuration changes what is visible.
UsesConfig bool
// Expires is when the credential stops being good; zero: never.
Expires time.Time
// Reload reads the caller's access again and returns what each subscription (by id) shows
// now. Ended means the credential is no longer valid.
Reload func(ctx context.Context) (Reloaded, error)
}
Access ties a stream to who is on the other end, so changes of that access reach it.
type Frame ¶
type Frame struct {
ID string // a resume token; set on the last frame of a change
Event string
Data []byte
}
Frame is one server-sent event. Event "" with no data is a heartbeat.
type Hub ¶
type Hub struct {
// Feed is the instance's shared consumer; every stream is a handler of it.
Feed *changefeed.Lazy
// Replay makes a consumer that reads from a resume position, over the same set the Feed
// reads. Nil disables resuming: a token is taken as expired history.
Replay func() *changefeed.Consumer
// ReloadJitter spreads the reads of access after a change (up to this long, per stream; default
// 250ms, negative for none) and ReloadConcurrency bounds how many run at once (default 16).
ReloadJitter time.Duration
// RefreshEvery is how often an open stream reads its caller's access again with nothing having
// changed: access that ends by the clock (a role window, a rule on `now`) has no event.
// Default 5 minutes; negative disables it.
RefreshEvery time.Duration
ReloadConcurrency int
Limits Limits
Observer Observer
Now func() time.Time
// contains filtered or unexported fields
}
Hub holds the open streams of one instance.
func (*Hub) Change ¶
func (h *Hub) Change(_ context.Context, c changefeed.Change, at changefeed.Position)
Change is the hub as the feed's handler: it finds, once per change, the subscriptions the change may matter to, and hands each stream its own.
func (*Hub) ConfigChanged ¶
ConfigChanged tells the streams that subscriptions to these collections (keyed by MongoDB database and collection) no longer follow the configuration they were opened with: they are removed from their streams and the client is told to subscribe again.
func (*Hub) Gap ¶
func (h *Hub) Gap(context.Context, changefeed.Gap)
Gap is called when the feed lost history: whatever the streams missed is gone.
func (*Hub) Idle ¶
func (h *Hub) Idle(context.Context, changefeed.Position)
Idle is part of changefeed.Handler: nothing to deliver.
type Key ¶
Key is an equality a subscription can only be visible under: every document it shows has one of Values at Field. It is what lets a change find the subscriptions it can matter to without asking all of them.
func KeysOf ¶
KeysOf extracts the keys of a filter: its top-level conditions (under And) that are an equality or a membership on a field. A condition under Or or Not is not a key, because the subscription may be visible without it. The result is only a shortcut: the whole filter still decides every candidate.
type Limits ¶
type Limits struct {
SubscriptionsPerStream int
StreamsPerUser int
Streams int // per instance
BufferBytes int // per stream
BufferEvents int // per stream
Memory int64
Heartbeat time.Duration
// OpenInterval is the least time between two streams opened by one principal.
OpenInterval time.Duration
// ResumeMaxAge is how old a resume token may be before the history is taken as gone.
ResumeMaxAge time.Duration
}
Limits are what bounds a deployment's streams.
type Observer ¶
type Observer interface {
StreamOpened(realm string)
StreamClosed(realm string)
Event(realm, event string)
Reset(realm, reason string)
Refused(realm, reason string)
// Checks counts the subscriptions a change was evaluated against: kind "indexed" (found by
// document id or key) or "scanned" (no key: asked on every change).
Checks(realm, kind string, n int)
}
Observer receives the realtime metrics; every method may be called on a nil receiver by its implementation.
type Reloaded ¶
type Reloaded struct {
Ended bool
Fingerprint string
Orgs []string
Expires time.Time
Subs map[string]Visibility
}
Reloaded is the answer of Access.Reload.
type Request ¶
type Request struct {
Realm string
// Principal names who the stream is for ("user:u_1", "key:ci", "anon:<ip>"); streams per
// caller and resume tokens are counted and bound by it.
Principal string
Key []byte // the realm's resume key
Subscriptions []*Subscription
Resume string // a token, or ""
// Limits lower the instance's: per stream subscriptions and streams per user, from the realm.
MaxStreamsPerUser int
// Access ties the stream to its caller's session, user or key, so that changes of them
// reach it; nil for anonymous callers and realms without authentication.
Access *Access
}
Request opens a stream.
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream is one open connection. It is a changefeed.Handler of the instance's shared feed.
func (*Stream) Close ¶
func (s *Stream) Close()
Close ends the stream and releases what it holds. It is safe to call more than once.
func (*Stream) Done ¶
func (s *Stream) Done() <-chan struct{}
Done is closed when the stream is closed.
type Subscription ¶
type Subscription struct {
ID string
// DB and Coll name what a change reports: the MongoDB database and the collection.
DB, Coll string
Doc string // a document id; "" follows the collection
Visible func(doc map[string]any) (bool, error)
// Render turns a stored document into what a read returns.
Render func(doc map[string]any) map[string]any
// Keys are the equalities the subscription can only be visible under (KeysOf of its
// resolved filter): they let a change find it without asking every subscription.
Keys []Key
// contains filtered or unexported fields
}
Subscription is one thing a stream follows: a collection filtered by Where, or one document of it. Visible is what decides, for the stream's caller, whether a document is part of it: the subscription's filter, the collection's read rule and soft deletion, evaluated in memory on the document as stored.
type Token ¶
type Token struct {
Pos []byte `json:"p"`
Realm string `json:"r"`
Principal string `json:"u"`
Issued int64 `json:"t"` // unix seconds
// Access fingerprints what the caller could see when the token was issued; a resume under
// another one (a role or membership changed meanwhile) reloads instead of replaying.
Access string `json:"a,omitempty"`
}
Token is a place in the change history that a client can resume after. It is signed with a per-realm key, so any instance can verify it after a reconnect and nobody can forge one or present another's.