Documentation
¶
Overview ¶
Package mercure helps implement the Mercure protocol (https://mercure.rocks) in Go projects. It provides an implementation of a Mercure hub as an HTTP handler.
Example ¶
package main
import (
"context"
"log"
"net/http"
"github.com/dunglas/mercure"
)
func main() {
ctx := context.Background()
h, err := mercure.NewHub(
ctx,
mercure.WithIssuers([]mercure.Issuer{{
Identifier: "https://example.com",
Publisher: mercure.Static{Key: []byte("!ChangeMe!"), Algorithm: "HS256"},
Subscriber: mercure.Static{Key: []byte("!ChangeMe!"), Algorithm: "HS256"},
}}),
mercure.WithResourceIdentifier("https://example.com/.well-known/mercure"),
)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := h.Stop(ctx); err != nil {
panic(err)
}
}()
http.Handle("/.well-known/mercure", h)
log.Panic(http.ListenAndServe(":8080", nil))
}
Output:
Index ¶
- Constants
- Variables
- func NewRequestOriginContext(ctx context.Context, scheme, host string) context.Context
- func NewSlogHandler(handler slog.Handler) slog.Handler
- type BoltTransport
- func (t *BoltTransport) AddSubscriber(ctx context.Context, s *LocalSubscriber) error
- func (t *BoltTransport) Close(_ context.Context) (err error)
- func (t *BoltTransport) Dispatch(ctx context.Context, update *Update) error
- func (t *BoltTransport) GetSubscribers(_ context.Context) (string, []*Subscriber, error)
- func (t *BoltTransport) RemoveSubscriber(_ context.Context, s *LocalSubscriber) error
- type Event
- type Hub
- func (h *Hub) DebuggerConfigHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) Drain()
- func (h *Hub) Playground(w http.ResponseWriter, r *http.Request)
- func (h *Hub) PlaygroundTokenHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) ProtectedResourceMetadataHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) Publish(ctx context.Context, update *Update) error
- func (h *Hub) PublishHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (h *Hub) Stop(ctx context.Context) error
- func (h *Hub) SubscribeHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) SubscriptionHandler(w http.ResponseWriter, r *http.Request)
- func (h *Hub) SubscriptionsHandler(w http.ResponseWriter, r *http.Request)
- type Issuer
- type KeyFunc
- type LocalSubscriber
- func (s *LocalSubscriber) Disconnect()
- func (s *LocalSubscriber) Dispatch(ctx context.Context, u *Update, fromHistory bool) bool
- func (s *LocalSubscriber) HistoryDispatched(responseLastEventID string)
- func (s *LocalSubscriber) Ready(ctx context.Context) (n int)
- func (s *LocalSubscriber) Receive() <-chan *Update
- type LocalTransport
- func (t *LocalTransport) AddSubscriber(ctx context.Context, s *LocalSubscriber) error
- func (t *LocalTransport) Close(_ context.Context) (err error)
- func (t *LocalTransport) Dispatch(ctx context.Context, update *Update) error
- func (t *LocalTransport) GetSubscribers(_ context.Context) (string, []*Subscriber, error)
- func (t *LocalTransport) RemoveSubscriber(_ context.Context, s *LocalSubscriber) error
- type MatcherType
- type Metrics
- type NopMetrics
- type Option
- func WithAnonymous() Option
- func WithCORSOrigins(origins []string) Option
- func WithClientIPFunc(f func(*http.Request) string) Option
- func WithCookieName(cookieName string) Option
- func WithDebug() Option
- func WithDebugger() Option
- func WithDispatchTimeout(timeout time.Duration) Option
- func WithDrainTimeout(timeout time.Duration) Option
- func WithHeartbeat(interval time.Duration) Option
- func WithIssuers(issuers []Issuer) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxRequestBodySize(size int64) Option
- func WithMetrics(m Metrics) Option
- func WithPlayground() Option
- func WithPlaygroundTokenFunc(f func(resourceIdentifier string) (string, error)) Option
- func WithProtocolVersionCompatibility(protocolVersionCompatibility int) Option
- func WithPublicURLs(urls []string) Option
- func WithPublishOrigins(origins []string) Option
- func WithResourceIdentifier(resourceIdentifier string) Option
- func WithSubscriptionLimits(limits SubscriptionLimits) Option
- func WithSubscriptions() Option
- func WithTopicMatcherStore(tms *TopicMatcherStore) Option
- func WithTransport(t Transport) Option
- func WithWriteTimeout(timeout time.Duration) Option
- type PrometheusMetrics
- type RequestOrigin
- type Static
- type Subscriber
- type SubscriberList
- type SubscriptionLimits
- type TopicMatcher
- type TopicMatcherStore
- type Transport
- type TransportError
- type TransportHealthChecker
- type TransportSubscribers
- type TransportTopicMatcherStore
- type Update
- type Verifier
Examples ¶
Constants ¶
const ( DefaultWriteTimeout = 600 * time.Second DefaultDispatchTimeout = 5 * time.Second DefaultHeartbeat = 40 * time.Second // DefaultMaxRequestBodySize bounds the publish and QUERY subscribe request // bodies; larger requests are rejected with a 413 as the protocol requires. DefaultMaxRequestBodySize int64 = 1 << 20 // 1 MiB )
const BoltDefaultCleanupFrequency = 0.3
const DefaultSubscriberListCacheSize = 100_000
DefaultSubscriberListCacheSize is the default size of the skipfilter cache.
Keys are fixed-size digests, so a full cache of 100,000 filters with no subscribers uses about 25MB whatever the size of the update topics.
const DefaultTopicMatcherStoreCacheSize = 100_000
DefaultTopicMatcherStoreCacheSize bounds the (matcher_type, pattern, topics) -> bool match cache and each compiled-pattern cache. At ~100 B/entry, 100_000 keeps each cache under ~10 MB. Raise it via the `topic_matcher_cache <N>` Caddyfile directive for hubs handling a much larger topic / matcher universe.
const EarliestLastEventID = "earliest"
EarliestLastEventID is the reserved value representing the earliest available event id.
const PlaygroundURLPrefix = "/playground/"
PlaygroundURLPrefix is the root path the insecure playground echo endpoints are served under (only when the playground is enabled). It sits outside the reserved /.well-known/mercure namespace, so the resources it serves are valid, subscribable Mercure topics; nesting them under the hub path would make them reserved.
INSECURE + EXPERIMENTAL. Not covered by the backward compatibility promise.
const ProtectedResourceMetadataPath = protectedResourceMetadataPath
ProtectedResourceMetadataPath is the RFC 9728 well-known path the hub serves its protected resource metadata at. It is exported so an embedding server (for example, the Caddy module) can route to it without re-deriving the path.
Variables ¶
var ( // ErrInvalidAuthorizationHeader is returned when the Authorization header is invalid. ErrInvalidAuthorizationHeader = errors.New(`invalid "Authorization" HTTP header`) // ErrNoOrigin is returned when the cookie authorization mechanism is used and no Origin nor Referer headers are presents. ErrNoOrigin = errors.New(`an "Origin" or a "Referer" HTTP header must be present to use the cookie-based authorization mechanism`) // ErrOriginNotAllowed is returned when the Origin is not allowed to post updates. ErrOriginNotAllowed = errors.New("origin not allowed to post updates") // ErrInvalidJWT is returned when the access token is invalid. ErrInvalidJWT = errors.New("invalid JWT") )
var ( ErrReservedTopic = errors.New(`topic value resolves into the reserved "/.well-known/mercure" namespace`) ErrReservedWildcard = errors.New(`topic value "*" is reserved for the wildcard matcher and cannot be published`) ErrInvalidEventID = errors.New(`"id" field is too long, contains a forbidden control character or invalid UTF-8, starts with "#", or is the reserved value "earliest"`) ErrInvalidEventType = errors.New(`"type" field contains a forbidden control character or invalid UTF-8`) ErrReservedEventType = errors.New(`"type" field uses the reserved value "mercure"`) ErrInvalidTopic = errors.New("topic is too long, or contains a forbidden control character or invalid UTF-8") ErrTooManyTopics = errors.New("too many topics in update") ErrMissingTopic = errors.New("update carries no topic") ErrInvalidData = errors.New(`"data" field is not valid UTF-8`) )
Sentinel errors returned by Publish. Callers can branch on them via errors.Is.
var ErrClosedTransport = errors.New("hub: read/write on closed Transport")
ErrClosedTransport is returned by the Transport's Dispatch and AddSubscriber methods after a call to Close.
var ErrConflictingBaseURL = errors.New("topic matcher store already configured with a different base URL")
ErrConflictingBaseURL is returned by setBaseURL (via NewHub) when a store already configured with one base URL is reused by a hub with a different public URL. The base URL is immutable configuration; sharing a store across hubs that disagree on it would silently corrupt relative-pattern matching, so it is rejected at construction instead.
var ErrDuplicateIssuer = errors.New("duplicate issuer identifier")
ErrDuplicateIssuer is returned when the same issuer identifier is configured more than once.
var ErrInvalidBaseURL = errors.New("base URL must be an absolute URL")
ErrInvalidBaseURL is returned by setBaseURL (via NewHub) when the configured public URL is not an absolute URL. Relative URL patterns and topics are resolved against it, so an invalid value would make every relative-pattern match fail at request time with an opaque error; it is rejected at construction instead.
var ErrInvalidPublicURL = errors.New("a public URL must be an absolute URL with a scheme and a host")
ErrInvalidPublicURL is returned when a public URL in the allowlist is not an absolute URL with a scheme and a host.
var ErrInvalidResourceIdentifier = errors.New("the resource identifier must be an absolute URL without a fragment (RFC 9728)")
ErrInvalidResourceIdentifier is returned when the configured resource identifier is not an RFC 9728 protected resource identifier: an absolute URL without a fragment component.
var ErrInvalidSubscriptionLimits = errors.New("subscription limits must be non-negative")
ErrInvalidSubscriptionLimits rejects negative subscription limits.
var ErrIssuerMissingKey = errors.New("an issuer must configure a publisher or subscriber verifier")
ErrIssuerMissingKey is returned when an issuer has no verifier for either role, so no token could ever be verified for it.
var ErrMissingAlgorithm = errors.New("a Static verifier requires a signing algorithm")
ErrMissingAlgorithm is returned when a Static verifier is configured without a signing algorithm.
var ErrMissingIssuerIdentifier = errors.New("an issuer identifier is required in modern mode")
ErrMissingIssuerIdentifier is returned when an issuer is configured in modern mode with an empty identifier: the token iss claim is matched against it, so it must be set.
var ErrMissingKey = errors.New("a Static verifier requires a key")
ErrMissingKey is returned when a Static verifier has no key material: an HMAC algorithm accepts a zero-length secret, so every token would verify.
var ErrNegativeDrainTimeout = errors.New("the drain timeout must not be negative")
ErrNegativeDrainTimeout is returned when the drain timeout is negative.
var ErrPEMKeyHMACAlgorithm = errors.New("a PEM-encoded key must not be used with an HMAC algorithm")
ErrPEMKeyHMACAlgorithm is returned when a Static verifier pairs a PEM key with an HMAC algorithm, which would make the public key the shared secret.
var ErrSpanningPublishOrigin = errors.New(`a wildcard publish origin must stay within one registrable domain, such as "https://*.example.com"`)
ErrSpanningPublishOrigin is returned when a wildcard publish origin matches origins outside one registrable domain, which would let unrelated sites publish with the victim's cookie.
var ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
ErrUnexpectedSigningMethod is returned when the signing JWT method is not supported.
var ErrUnsupportedMatcherType = errors.New("unsupported topic matcher type")
ErrUnsupportedMatcherType is returned when a matcher type is not one of the types defined by the protocol. The HTTP handlers map it to a 400 status code.
var ErrUnsupportedProtocolVersion = errors.New("compatibility mode only supports protocol versions 7 and 8")
ErrUnsupportedProtocolVersion is returned when the version passed is unsupported.
var SubscriberContextKey subscriberContextKeyType //nolint:gochecknoglobals
var UpdateContextKey updateContextKeyType //nolint:gochecknoglobals
Functions ¶
func NewRequestOriginContext ¶ added in v1.0.0
NewRequestOriginContext returns a copy of ctx carrying the resolved request origin. An embedding server (for example the Caddy module) calls it once per request before invoking the hub, passing scheme and host taken from a trusted source; the hub validates them against the public-URL allowlist. The Caddy module reads them from Caddy's request placeholders, which report the request's own Host and TLS state, overridden by the X-Forwarded-Proto and X-Forwarded-Host headers of requests from trusted_proxies only.
func NewSlogHandler ¶ added in v0.21.0
INTERNAL: NewSlogHandler returns a log/slog.Handler that automatically appends "update" and "subscriber" context, if applicable.
This function will be removed when https://github.com/caddyserver/caddy/pull/7346 will be available.
Types ¶
type BoltTransport ¶
BoltTransport implements the TransportInterface using the Bolt database.
func NewBoltTransport ¶
func NewBoltTransport( subscriberList *SubscriberList, logger *slog.Logger, path string, bucketName string, size uint64, cleanupFrequency float64, ) (*BoltTransport, error)
NewBoltTransport creates a new BoltTransport.
func (*BoltTransport) AddSubscriber ¶
func (t *BoltTransport) AddSubscriber(ctx context.Context, s *LocalSubscriber) error
AddSubscriber adds a new subscriber to the transport.
func (*BoltTransport) Close ¶
func (t *BoltTransport) Close(_ context.Context) (err error)
Close closes the Transport.
func (*BoltTransport) Dispatch ¶
func (t *BoltTransport) Dispatch(ctx context.Context, update *Update) error
Dispatch dispatches an update to all subscribers and persists it in Bolt DB.
func (*BoltTransport) GetSubscribers ¶
func (t *BoltTransport) GetSubscribers(_ context.Context) (string, []*Subscriber, error)
GetSubscribers get the list of active subscribers.
func (*BoltTransport) RemoveSubscriber ¶ added in v0.14.0
func (t *BoltTransport) RemoveSubscriber(_ context.Context, s *LocalSubscriber) error
RemoveSubscriber removes a new subscriber from the transport.
type Event ¶
type Event struct {
// The updates' data, encoded in the sever-sent event format: every line starts with the string "data: "
// https://www.w3.org/TR/eventsource/#dispatchMessage
Data string
// The globally unique identifier corresponding to update
ID string
// The event type, will be attached to the "event" field
Type string
// The reconnection time
Retry uint64
}
Event is the actual Server Sent Event that will be dispatched.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub stores channels with clients currently subscribed and allows to dispatch updates.
func (*Hub) DebuggerConfigHandler ¶ added in v1.0.0
func (h *Hub) DebuggerConfigHandler(w http.ResponseWriter, r *http.Request)
DebuggerConfigHandler serves the bundled UI's runtime configuration as JSON. It exposes only hub state the UI needs to adapt itself, never a token. Registered whenever the debugger is enabled (see WithDebugger).
func (*Hub) Drain ¶ added in v1.1.0
func (h *Hub) Drain()
Drain closes subscribers at random times within the drain timeout; call it on termination only, never on reload.
func (*Hub) Playground ¶ added in v1.0.0
func (h *Hub) Playground(w http.ResponseWriter, r *http.Request)
Playground exposes INSECURE endpoints to test discovery and authorization mechanisms. Add a query parameter named "body" to define the content to return in the response's body. Add a query parameter named "jwt" to set the authorization cookie (see WithCookieName) containing this token. Responses use JSON or JSON-LD for .json or .jsonld paths, and plain text otherwise.
func (*Hub) PlaygroundTokenHandler ¶ added in v1.0.0
func (h *Hub) PlaygroundTokenHandler(w http.ResponseWriter, r *http.Request)
PlaygroundTokenHandler returns a freshly minted access token for the debugger UI to prefill, with the aud claim set to the resource identifier derived for this request (so it is valid on whatever public URL the playground answers on).
INSECURE: the token grants publish and subscribe on every topic; it exists only to make the playground self-serve. Registered only when the playground is enabled and a signing callback is configured (see WithPlayground, WithPlaygroundTokenFunc).
EXPERIMENTAL. Not covered by the backward compatibility promise.
func (*Hub) ProtectedResourceMetadataHandler ¶ added in v1.0.0
func (h *Hub) ProtectedResourceMetadataHandler(w http.ResponseWriter, r *http.Request)
ProtectedResourceMetadataHandler serves the hub's RFC 9728 protected resource metadata document.
func (*Hub) Publish ¶ added in v0.21.0
Publish broadcasts the given update to all subscribers. Publish takes ownership of update: transports may deliver it asynchronously, so callers must not modify it, including its Topics slice, after the call. The transport may set its ID.
func (*Hub) PublishHandler ¶
func (h *Hub) PublishHandler(w http.ResponseWriter, r *http.Request)
PublishHandler allows publisher to broadcast updates to all subscribers.
func (*Hub) SubscribeHandler ¶
func (h *Hub) SubscribeHandler(w http.ResponseWriter, r *http.Request)
SubscribeHandler creates a keep alive connection and sends the events to the subscribers.
func (*Hub) SubscriptionHandler ¶
func (h *Hub) SubscriptionHandler(w http.ResponseWriter, r *http.Request)
func (*Hub) SubscriptionsHandler ¶
func (h *Hub) SubscriptionsHandler(w http.ResponseWriter, r *http.Request)
type Issuer ¶ added in v1.0.0
type Issuer struct {
// Identifier is the exact value of the token iss claim.
Identifier string
// AuthorizationServer advertises Identifier in the hub's RFC 9728 protected
// resource metadata. Leave false for self-issued tokens (a key shared out
// of band, no authorization server).
AuthorizationServer bool
// Publisher and Subscriber verify tokens for each role. A nil Verifier
// means the role is not accepted for this issuer.
Publisher Verifier
Subscriber Verifier
}
Issuer binds a trusted issuer (RFC 9068 §4) to its per-role verification material. Configure issuers with WithIssuers.
type KeyFunc ¶ added in v1.0.0
type KeyFunc struct {
// Keyfunc supplies the verification key(s) for each token.
Keyfunc jwt.Keyfunc
// Algorithms pins the accepted JWS algorithms (RFC 8725): the algorithm is
// never taken from the token header. Defaults to the asymmetric allowlist
// when empty, so a public JWK can never be reinterpreted as an HMAC secret.
Algorithms []string
}
KeyFunc verifies tokens with a caller-supplied keyfunc, typically backed by a JWK Set.
type LocalSubscriber ¶ added in v0.17.0
type LocalSubscriber struct {
Subscriber
// contains filtered or unexported fields
}
LocalSubscriber represents a client subscribed to a list of topics on the current hub.
func NewLocalSubscriber ¶ added in v0.17.0
func NewLocalSubscriber(lastEventID string, logger *slog.Logger, topicMatcherStore *TopicMatcherStore) *LocalSubscriber
NewLocalSubscriber creates a new subscriber.
func (*LocalSubscriber) Disconnect ¶ added in v0.17.0
func (s *LocalSubscriber) Disconnect()
Disconnect disconnects the subscriber.
func (*LocalSubscriber) Dispatch ¶ added in v0.17.0
Dispatch an update to the subscriber. Security checks must (topics matching) be done before calling Dispatch, for instance by calling Match.
func (*LocalSubscriber) HistoryDispatched ¶ added in v0.17.0
func (s *LocalSubscriber) HistoryDispatched(responseLastEventID string)
HistoryDispatched must be called when all messages coming from the history have been dispatched.
func (*LocalSubscriber) Ready ¶ added in v0.17.0
func (s *LocalSubscriber) Ready(ctx context.Context) (n int)
Ready flips the ready flag to true and flushes queued live updates returning number of events flushed.
func (*LocalSubscriber) Receive ¶ added in v0.17.0
func (s *LocalSubscriber) Receive() <-chan *Update
Receive returns a chan when incoming updates are dispatched.
type LocalTransport ¶
LocalTransport implements the TransportInterface without database and simply broadcast the live Updates.
func NewLocalTransport ¶
func NewLocalTransport(sl *SubscriberList) *LocalTransport
NewLocalTransport creates a new LocalTransport.
func (*LocalTransport) AddSubscriber ¶
func (t *LocalTransport) AddSubscriber(ctx context.Context, s *LocalSubscriber) error
AddSubscriber adds a new subscriber to the transport.
func (*LocalTransport) Close ¶
func (t *LocalTransport) Close(_ context.Context) (err error)
Close closes the Transport.
func (*LocalTransport) Dispatch ¶
func (t *LocalTransport) Dispatch(ctx context.Context, update *Update) error
Dispatch dispatches an update to all subscribers.
func (*LocalTransport) GetSubscribers ¶
func (t *LocalTransport) GetSubscribers(_ context.Context) (string, []*Subscriber, error)
GetSubscribers gets the list of active subscribers.
func (*LocalTransport) RemoveSubscriber ¶ added in v0.14.0
func (t *LocalTransport) RemoveSubscriber(_ context.Context, s *LocalSubscriber) error
RemoveSubscriber removes a subscriber from the transport.
type MatcherType ¶ added in v1.0.0
type MatcherType string
MatcherType identifies how a topic matcher pattern is evaluated. The constant values are canonical: they double as the wire format in subscription API URLs, subscription event payloads and JWT claims.
const ( // MatcherTypeExact selects byte-for-byte topic comparison. The reserved // pattern "*" matches every topic. MatcherTypeExact MatcherType = "exact" // MatcherTypeURLPattern selects matching per the WHATWG URL Pattern // Living Standard, with the hub's public URL as the base URL. MatcherTypeURLPattern MatcherType = "urlpattern" )
type Metrics ¶
type Metrics interface {
// SubscriberConnected collects metrics about subscriber connections.
SubscriberConnected(s *LocalSubscriber)
// SubscriberDisconnected collects metrics about subscriber disconnections.
SubscriberDisconnected(s *LocalSubscriber)
// UpdatePublished collects metrics about update publications.
UpdatePublished(u *Update)
}
type NopMetrics ¶
type NopMetrics struct{}
func (NopMetrics) SubscriberConnected ¶
func (NopMetrics) SubscriberConnected(_ *LocalSubscriber)
func (NopMetrics) SubscriberDisconnected ¶
func (NopMetrics) SubscriberDisconnected(_ *LocalSubscriber)
func (NopMetrics) UpdatePublished ¶
func (NopMetrics) UpdatePublished(_ *Update)
type Option ¶
type Option func(o *opt) error
Option instances allow to configure the library.
func WithAnonymous ¶
func WithAnonymous() Option
WithAnonymous allows subscribers with no valid JWT.
func WithCORSOrigins ¶
WithCORSOrigins sets the allowed CORS origins.
func WithClientIPFunc ¶ added in v1.1.0
WithClientIPFunc sets how the hub identifies a client in logs and per-client subscription limits, such as a client IP resolved from trusted proxy headers. An empty result falls back to RemoteAddr.
func WithCookieName ¶ added in v0.14.0
WithCookieName sets the name of the authorization cookie (defaults to "__Secure-mercure_access_token"). The default "__Secure-" prefix makes user agents refuse the cookie over insecure transport; plain-HTTP deployments (local development) must configure a prefix-less name.
func WithDebugger ¶ added in v1.0.0
func WithDebugger() Option
func WithDispatchTimeout ¶
WithDispatchTimeout sets maximum dispatch duration of an update.
func WithDrainTimeout ¶ added in v1.1.0
WithDrainTimeout sets the maximum duration of the drain started by Drain, defaults to 0 (disabled).
func WithHeartbeat ¶
WithHeartbeat sets the frequency of the SSE keep-alive comments, defaults to 40s, set to 0 to disable.
func WithIssuers ¶ added in v1.0.0
WithIssuers configures access-token verification, binding each trusted issuer (RFC 9068 §4) to its own verification material. It replaces the former per-role options: a token is verified only with the key(s) associated with its iss claim, so key material is never pooled across issuers.
Each Issuer provides a Publisher and/or Subscriber Verifier (a nil Verifier means that role is not accepted for the issuer). Setting AuthorizationServer advertises the issuer in the hub's RFC 9728 protected resource metadata; a self-issued issuer (a key shared out of band) leaves it false.
func WithMaxRequestBodySize ¶ added in v1.0.0
WithMaxRequestBodySize bounds the size, in bytes, of publish and QUERY subscribe request bodies; larger requests are rejected with a 413 status code. Defaults to DefaultMaxRequestBodySize, set to 0 to disable the in-hub limit (for example, when a reverse proxy already enforces one).
func WithMetrics ¶
WithMetrics enables collection of Prometheus metrics.
func WithPlayground ¶ added in v1.0.0
func WithPlayground() Option
WithPlayground enables the playground.
func WithPlaygroundTokenFunc ¶ added in v1.0.0
WithPlaygroundTokenFunc sets the callback the playground UI uses to prefill an access token, exposed at .well-known/mercure/debug/playground-token. The callback receives the request's resource identifier (the token's aud claim, derived per request so the token is valid on whatever public URL the playground answers on). It has no effect unless the playground is also enabled (see WithPlayground).
INSECURE: it hands every visitor an all-access token (publish and subscribe on every topic) signed with the hub's key, so only ever configure it on a throwaway playground hub. The Caddy module wires it automatically for a `playground` hub that has a symmetric signing key.
EXPERIMENTAL. Not covered by the backward compatibility promise.
func WithProtocolVersionCompatibility ¶ added in v0.14.0
WithProtocolVersionCompatibility sets the version of the Mercure protocol to be backward compatible with (versions 7 and 8 are supported). The v8 behaviors (URI Template selectors in the `topic` parameter, bare-string JWT claims, alternate topics, v8 subscription routes) additionally require a hub binary built with the deprecated_topic tag.
func WithPublicURLs ¶ added in v1.0.0
WithPublicURLs restricts the hub to the given public URLs, pinning their scheme as well as their host. A request whose derived origin (scheme + host) is not one of them is rejected with 421 Misdirected Request, and the hub never derives an identity from an unlisted origin. This is the safety gate for a catch-all site block, where the fronting server does not already constrain the Host. Each value is an absolute URL; only its scheme and host (with optional port) are significant.
func WithPublishOrigins ¶
WithPublishOrigins sets the origins allowed to publish updates.
func WithResourceIdentifier ¶ added in v1.0.0
WithResourceIdentifier pins the hub's OAuth 2.0 resource identifier (RFC 9068 `aud` value, advertised through RFC 9728 protected resource metadata) to a single static value. When unset, the hub derives the identifier from each request (the public URL the client contacted), so a hub reachable through several public URLs needs no configuration; set this only to force one canonical audience shared across every URL. A value ending in "/.well-known/mercure" also becomes the base URL for matching relative URL patterns and topics.
func WithSubscriptionLimits ¶ added in v1.1.0
func WithSubscriptionLimits(limits SubscriptionLimits) Option
WithSubscriptionLimits sets the concurrent HTTP subscription limits.
func WithSubscriptions ¶
func WithSubscriptions() Option
WithSubscriptions allows to dispatch updates when subscriptions are created or terminated.
func WithTopicMatcherStore ¶ added in v1.0.0
func WithTopicMatcherStore(tms *TopicMatcherStore) Option
WithTopicMatcherStore sets the TopicMatcherStore instance to use.
func WithTransport ¶
WithTransport sets the transport to use.
func WithWriteTimeout ¶
WithWriteTimeout sets maximum duration before closing the connection, defaults to 600s, set to 0 to disable.
type PrometheusMetrics ¶
type PrometheusMetrics struct {
// contains filtered or unexported fields
}
PrometheusMetrics store Hub collected metrics.
func NewPrometheusMetrics ¶
func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics
NewPrometheusMetrics creates a Prometheus metrics collector. This method must be called only one time, or it will panic.
func (*PrometheusMetrics) SubscriberConnected ¶
func (m *PrometheusMetrics) SubscriberConnected(_ *LocalSubscriber)
func (*PrometheusMetrics) SubscriberDisconnected ¶
func (m *PrometheusMetrics) SubscriberDisconnected(_ *LocalSubscriber)
func (*PrometheusMetrics) SubscriptionRejected ¶ added in v1.1.0
func (m *PrometheusMetrics) SubscriptionRejected(reason string)
SubscriptionRejected records a rejected stream using a bounded reason label (total, token, or client), without exposing tokens or client addresses. Custom Metrics implementations may optionally implement this method.
func (*PrometheusMetrics) UpdatePublished ¶
func (m *PrometheusMetrics) UpdatePublished(_ *Update)
type RequestOrigin ¶ added in v1.0.0
RequestOrigin is the scheme and host an embedding server resolved for the current request. The hub derives its per-request OAuth resource identifier and RFC 9728 protected resource metadata URL from it (see requestIdentity).
type Static ¶ added in v1.0.0
type Static struct {
// Key is the verification key: an HMAC secret or a PEM-encoded public key.
Key []byte
// Algorithm is the single accepted JWS algorithm (e.g. "HS256", "RS256").
Algorithm string
}
Static verifies tokens with a single embedded key.
type Subscriber ¶
type Subscriber struct {
ID string
EscapedID string
Claims *claims
RequestLastEventID string
// RequestLastEventIDSet reports whether the request carried a
// Last-Event-ID header or a last_event_id query parameter at all, even
// with an empty value: the protocol requires answering with a
// Mercure-Last-Event-ID response field whenever one was present.
RequestLastEventIDSet bool
// SubscribedMatchers are the topic matchers from the topic and
// match_urlpattern query parameters (or from the v8 `topic` parameter,
// which resolves to a deprecated matcher under compatibility mode).
SubscribedMatchers []TopicMatcher
// AllowedPrivateMatchers are the topic matchers from the JWT claims.
AllowedPrivateMatchers []TopicMatcher
// EscapedMatchers are precomputed "escapedType/escapedPattern" slugs for
// subscription URLs.
EscapedMatchers []string
// SubscriptionPayloads holds the `payload` resolved per
// subscribed matcher at registration time, indexed parallel to
// SubscribedMatchers. Precomputing lets the subscription API render
// payloads for subscribers a transport reconstructed from its
// persistence layer without doing live matcher dispatch on the
// deserialized object.
SubscriptionPayloads []any
// contains filtered or unexported fields
}
Subscriber represents a client subscribed to a list of topics on a remote or on the current hub.
func NewSubscriber ¶
func NewSubscriber(logger *slog.Logger, topicMatcherStore *TopicMatcherStore) *Subscriber
func (*Subscriber) LogValue ¶ added in v0.21.0
func (s *Subscriber) LogValue() slog.Value
func (*Subscriber) Match ¶ added in v0.14.0
func (s *Subscriber) Match(u *Update) bool
Match checks if the current subscriber can receive the given update.
func (*Subscriber) MatchTopics ¶ added in v0.14.0
func (s *Subscriber) MatchTopics(topics []string, private bool) bool
MatchTopics checks if the current subscriber can access to at least one of the given topics.
func (*Subscriber) SetMatchers ¶ added in v1.0.0
func (s *Subscriber) SetMatchers(subscribed, allowedPrivate []TopicMatcher)
SetMatchers sets the subscribed and allowed-private topic matchers and recomputes the derived subscription slugs and payloads, keeping the parallel SubscribedMatchers / EscapedMatchers / SubscriptionPayloads slices consistent. Transport implementations that reconstruct a Subscriber on another node use it to populate matchers programmatically, instead of relying on the exported fields (whose parallel-slice invariant is not otherwise enforced).
type SubscriberList ¶ added in v0.14.0
type SubscriberList struct {
// contains filtered or unexported fields
}
func NewSubscriberList ¶ added in v0.14.0
func NewSubscriberList(cacheSize int) *SubscriberList
func (*SubscriberList) Add ¶ added in v0.14.0
func (sl *SubscriberList) Add(s *LocalSubscriber)
func (*SubscriberList) Len ¶ added in v0.14.0
func (sl *SubscriberList) Len() int
func (*SubscriberList) MatchAny ¶ added in v0.14.0
func (sl *SubscriberList) MatchAny(u *Update) []*LocalSubscriber
func (*SubscriberList) Remove ¶ added in v0.14.0
func (sl *SubscriberList) Remove(s *LocalSubscriber)
func (*SubscriberList) Walk ¶ added in v0.14.0
func (sl *SubscriberList) Walk(start uint64, callback func(s *LocalSubscriber) bool) uint64
type SubscriptionLimits ¶ added in v1.1.0
type SubscriptionLimits struct {
Total int `json:"total"`
PerToken int `json:"per_token"`
PerClient int `json:"per_client"`
}
SubscriptionLimits bounds concurrent HTTP event streams on one hub instance. A zero field disables that limit, and all are zero by default. Client limits use the address from WithClientIPFunc, or RemoteAddr, never untrusted forwarding headers; token limits count verified tokens.
type TopicMatcher ¶ added in v1.0.0
type TopicMatcher struct {
Type MatcherType
Pattern string
}
TopicMatcher pairs a matcher type with a pattern string. It is the exported value type transports use to (re)construct a Subscriber's matchers via Subscriber.SetMatchers.
type TopicMatcherStore ¶ added in v1.0.0
type TopicMatcherStore struct {
// contains filtered or unexported fields
}
TopicMatcherStore caches match results and compiled patterns. The match cache is a single unsharded otter instance; otter v2 is designed for high concurrency.
func NewTopicMatcherStore ¶ added in v1.0.0
func NewTopicMatcherStore(cacheSize int) (*TopicMatcherStore, error)
NewTopicMatcherStore creates a TopicMatcherStore. If cacheSize > 0, match results, compiled templates and compiled URL patterns are cached; otherwise nothing is memoised.
type Transport ¶
type Transport interface {
// Dispatch dispatches an update to all subscribers.
//
// It trusts u to be well-formed. A caller that builds u from untrusted
// input (e.g. a publisher request) and dispatches it directly instead of
// through Hub.Publish MUST call u.Validate with the matching base URL and
// reject the update on error, otherwise a CR, LF, or NUL in ID or Type can inject arbitrary SSE
// fields into subscribers' streams (CWE-93). Hub-internal updates such as
// subscription events are trusted and skip Validate (they use reserved
// topics that Validate rejects by design).
Dispatch(ctx context.Context, u *Update) error
// AddSubscriber adds a new subscriber to the transport.
// It must call s.HistoryDispatched exactly once when s.RequestLastEventIDSet is true, even for an empty RequestLastEventID: the subscribe handler blocks on it before sending headers.
AddSubscriber(ctx context.Context, s *LocalSubscriber) error
// RemoveSubscriber removes a subscriber from the transport.
RemoveSubscriber(ctx context.Context, s *LocalSubscriber) error
// Close closes the Transport.
Close(ctx context.Context) error
}
Transport provides methods to dispatch and persist updates.
type TransportError ¶ added in v0.12.2
type TransportError struct {
// contains filtered or unexported fields
}
TransportError is returned when the Transport's DSN is invalid.
func (*TransportError) Error ¶ added in v0.12.2
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶ added in v0.12.2
func (e *TransportError) Unwrap() error
type TransportHealthChecker ¶ added in v0.23.0
type TransportHealthChecker interface {
// Ready reports whether the transport can currently serve traffic.
// Returns nil if healthy, or an error describing the problem.
// This is typically used for readiness probes (e.g. Kubernetes).
Ready(ctx context.Context) error
// Live reports whether the transport is fundamentally operational.
// Returns nil if alive, or an error if the transport has been unhealthy
// for an extended period and should be restarted.
// This is typically used for liveness probes (e.g. Kubernetes).
Live(ctx context.Context) error
}
TransportHealthChecker may be implemented by transports that support health checking. Transports that do not implement this interface are assumed to always be healthy.
type TransportSubscribers ¶
type TransportSubscribers interface {
// GetSubscribers gets the last event ID and the list of active subscribers at this time.
GetSubscribers(ctx context.Context) (string, []*Subscriber, error)
}
TransportSubscribers provides a method to retrieve the list of active subscribers.
type TransportTopicMatcherStore ¶ added in v1.0.0
type TransportTopicMatcherStore interface {
SetTopicMatcherStore(store *TopicMatcherStore)
}
TransportTopicMatcherStore provides a method to pass the TopicMatcherStore to the transport.
type Update ¶
type Update struct {
// The Server-Sent Event to send.
Event
// The topics' Internationalized Resource Identifier (RFC3987) (will most
// likely be URLs). The first one is the canonical topic; any others are
// alternate topics. The update is dispatched to subscribers matching
// either the canonical topic or any alternate; a private update's
// audience is the union of the audiences of all its topics.
Topics []string
// Private updates can only be dispatched to subscribers authorized to receive them.
Private bool
// To print debug information
Debug bool
}
Update represents an update to send to subscribers.
func (*Update) AssignUUID ¶ added in v0.21.0
func (u *Update) AssignUUID()
AssignUUID generates a new UUID an assign it to the given update if no ID is already set.
func (*Update) SpanAttributes ¶ added in v0.24.0
SpanAttributes returns the OpenTelemetry attributes describing this update.
func (*Update) Validate ¶ added in v0.24.2
Validate enforces the publish-side input rules that protect subscribers from update forgery and SSE field injection. Hub.Publish calls it, so the bundled hub and PublishHandler are already covered.
A caller that builds an Update from untrusted input (e.g. a publisher request) and dispatches it through a Transport directly, bypassing Hub.Publish, MUST call Validate with the same base URL used for topic matching and reject the update on error. The base must be an absolute URL with a host. Skipping it lets a CR, LF, or NUL in ID or Type inject arbitrary SSE fields into subscribers' streams (CWE-93). Validate also rejects the reserved "/.well-known/mercure" topic namespace, so it is meant for publisher input, not hub-internal updates such as subscription events.
Source Files
¶
- authorization.go
- authorization_notdeprecated.go
- authorizationclaims_notdeprecated.go
- authorizationdetails.go
- bearererror.go
- bolt.go
- debugger.go
- event.go
- handler.go
- handlertopic_notdeprecated.go
- hub.go
- identity.go
- jwtkeyfunc.go
- local.go
- localsubscriber.go
- logging.go
- matcher_notdeprecated.go
- matchertype.go
- metrics.go
- playground.go
- publish.go
- reservedtopic.go
- resourcemetadata.go
- subscribe.go
- subscribematchers.go
- subscriber.go
- subscriberlist.go
- subscription.go
- subscriptionlimits.go
- topicmatcher.go
- topicmatcher_notdeprecated.go
- tracer.go
- transport.go
- update.go
