Documentation
¶
Overview ¶
Package websub implements WebSub and PubSubHubbub 0.4 for all three conformance classes: subscriber, publisher, and hub.
A Subscriber discovers a topic's hub, subscribes, answers the hub's verification challenge, and receives content distribution requests. A Publisher advertises its hub to discovering subscribers and pings that hub when a topic changes. A Hub accepts subscriptions, verifies intent, and fans content out.
All three implement net/http.Handler, so they mount directly on net/http.ServeMux, chi, and gorilla/mux. The adapter modules cover gin, echo, and fiber.
ModeWebSub is the default. Pass WithMode for ModePuSH04, which mainly relaxes lease handling. See mode.go for the full set of differences.
This module has no dependencies outside the standard library. Storage backends live in storage/*, and a protocol-speaking fake hub for your own tests lives in websubtest.
Index ¶
- Constants
- Variables
- func Sign(alg SignatureAlgorithm, secret string, body []byte) (string, error)
- func Verify(header, secret string, body []byte) error
- func WithMaxBodySize(n int64) interface{ ... }
- type DenialError
- type Discovery
- type Hooks
- type Hub
- func (h *Hub) AddSniffer(topic string, fn func(topic, contentType string, body []byte))
- func (h *Hub) AddValidator(fn func(*HubSubscription) (ok bool, reason string))
- func (h *Hub) Publish(ctx context.Context, topic, contentType string, body []byte) error
- func (h *Hub) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (h *Hub) Shutdown(ctx context.Context) error
- func (h *Hub) Subscriptions(ctx context.Context, topic string) ([]Subscription, error)
- func (h *Hub) Topics(ctx context.Context) ([]string, error)
- func (h *Hub) URL() string
- type HubOption
- func WithDeliveryConcurrency(n int) HubOption
- func WithDeliveryRetry(attempts int, backoff time.Duration) HubOption
- func WithHooks(h Hooks) HubOption
- func WithLeaseBounds(minLease, maxLease time.Duration) HubOption
- func WithSignatureAlgorithm(alg SignatureAlgorithm) HubOption
- func WithStore(s SubscriptionStore) HubOption
- type HubSubscription
- type Link
- type Mode
- type Option
- type Publisher
- func (p *Publisher) Advertise(topic string) http.Header
- func (p *Publisher) Hubs() []string
- func (p *Publisher) Middleware(topic string, next http.Handler) http.Handler
- func (p *Publisher) Publish(ctx context.Context, topic, contentType string, body []byte) error
- func (p *Publisher) TopicURL(topic string) string
- type PublisherOption
- type SignatureAlgorithm
- type SubscribeOption
- type Subscriber
- func (s *Subscriber) Await(ctx context.Context, sub *Subscription) (*Subscription, error)
- func (s *Subscriber) Close() error
- func (s *Subscriber) Discover(ctx context.Context, topicURL string) (*Discovery, error)
- func (s *Subscriber) Forget(sub *Subscription)
- func (s *Subscriber) OnNotify(fn func(sub *Subscription, contentType string, body []byte))
- func (s *Subscriber) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Subscriber) Subscribe(ctx context.Context, topic string, opts ...SubscribeOption) (*Subscription, error)
- func (s *Subscriber) Subscriptions() []Subscription
- func (s *Subscriber) Unsubscribe(ctx context.Context, sub *Subscription) error
- type SubscriberOption
- type Subscription
- type SubscriptionState
- type SubscriptionStore
Constants ¶
const ( DefaultMinLease = 5 * time.Minute DefaultMaxLease = 30 * 24 * time.Hour DefaultDeliveryAttempts = 3 DefaultDeliveryBackoff = time.Second DefaultDeliveryConcurrent = 16 )
Hub defaults. All are overridable by option.
const DefaultLease = 10 * 24 * time.Hour
DefaultLease is requested when the caller does not specify one. It matches the 10 day value PubSubHubbub 0.4 suggests, which sits inside the range hubs commonly accept.
const DefaultMaxBodySize = 8 << 20
DefaultMaxBodySize caps how much of a content distribution request body is read when WithMaxBodySize is not given.
const DefaultRenewAt = 0.8
DefaultRenewAt is the fraction of a granted lease after which a subscription is renewed. The remaining fifth is slack: enough for a failed attempt to be retried before the subscription actually lapses.
const DefaultSignatureAlgorithm = SHA256
DefaultSignatureAlgorithm is what a hub signs with unless configured otherwise. WebSub requires hubs to support SHA1 but recommends against it.
const MaxDiscoveryBody = 1 << 20
MaxDiscoveryBody caps how much of a discovery response this package reads. Discovery only needs headers and, for document based mechanisms, the head of the document, so a hostile or runaway topic URL cannot exhaust memory.
const MaxSecretLength = 200
MaxSecretLength is the longest secret WebSub permits. Subscribers must not send more, and hubs may reject more.
https://www.w3.org/TR/websub/#subscriber-sends-subscription-request
const SignatureHeader = "X-Hub-Signature"
SignatureHeader carries the HMAC of a content distribution request body.
https://www.w3.org/TR/websub/#authenticated-content-distribution
Variables ¶
var ( // ErrDiscoveryFailed means no hub and topic pair could be found for a // URL, either because the document advertised neither or because it // could not be fetched. ErrDiscoveryFailed = errors.New("websub: could not discover hub/topic") // ErrInvalidSignature means a content distribution request carried an // X-Hub-Signature that did not match the body, or omitted the header // entirely on a subscription that was created with a secret. ErrInvalidSignature = errors.New("websub: signature verification failed") // ErrSubscriptionDenied means the hub rejected the subscription, either // with a non-2xx response to the request or with a denial notification // delivered to the callback afterward. ErrSubscriptionDenied = errors.New("websub: hub denied subscription") // ErrLeaseOutOfRange means the requested lease fell outside the bounds // the hub is configured to accept and could not be clamped. ErrLeaseOutOfRange = errors.New("websub: requested lease outside allowed range") // ErrUnknownSubscription means a verification challenge or content // distribution request arrived for a topic and callback the subscriber // has no pending or active subscription for. ErrUnknownSubscription = errors.New("websub: no such subscription") )
Sentinel errors returned by this package. Compare with errors.Is; the errors returned to callers usually wrap these with more detail.
Functions ¶
func Sign ¶
func Sign(alg SignatureAlgorithm, secret string, body []byte) (string, error)
Sign returns an X-Hub-Signature value for body, keyed by secret.
func Verify ¶
Verify checks an X-Hub-Signature header value against body and secret. It returns nil only if the header is well formed, names a supported algorithm, and matches.
An empty header is an error whenever a secret is configured. WebSub requires subscribers to reject unsigned content on an authenticated subscription rather than fall back to trusting it.
func WithMaxBodySize ¶
func WithMaxBodySize(n int64) interface { SubscriberOption HubOption }
WithMaxBodySize caps how much of a content distribution request body is read, in bytes. Bodies larger than the cap are rejected rather than truncated, since a truncated body would fail signature verification anyway. The default is 8 MiB.
Types ¶
type DenialError ¶
DenialError is returned when a hub denies a subscription. WebSub allows a hub to state a reason, which is carried here when one was given.
https://www.w3.org/TR/websub/#subscriber-receives-denial
func (*DenialError) Error ¶
func (e *DenialError) Error() string
func (*DenialError) Is ¶
func (e *DenialError) Is(target error) bool
type Discovery ¶
type Discovery struct {
// Topic is the canonical topic URL, taken from the rel=self link. It
// falls back to the URL discovery finished at, following redirects, when
// no rel=self is advertised.
Topic string
// Hubs are the advertised rel=hub URLs, in the order found. WebSub
// permits more than one; a subscriber that wants redundancy subscribes
// at several, and one that does not uses the first.
Hubs []string
// ContentType is the media type of the discovery response, with
// parameters stripped.
ContentType string
}
Discovery is what a topic URL advertises about itself.
https://www.w3.org/TR/websub/#discovery
func Discover ¶
Discover fetches topicURL and reports the hub and canonical topic it advertises, using http.DefaultClient and ModeWebSub rules. Subscribers should call Subscriber.Discover instead so the configured client, mode, and logger apply.
type Hooks ¶
type Hooks struct {
// OnSubscribe fires after a subscription is verified and stored.
OnSubscribe func(sub HubSubscription)
// OnUnsubscribe fires after a subscription is verified as removed.
OnUnsubscribe func(sub HubSubscription)
// OnPublish fires once per publish, before any delivery is attempted.
OnPublish func(topic string)
// OnDeliveryFailure fires for each delivery attempt that fails, so it
// can fire several times for one subscriber across retries.
OnDeliveryFailure func(sub HubSubscription, err error)
}
Hooks are optional callbacks a Hub invokes as it works, for metrics or auditing. Any nil field is skipped, so set only the ones you need.
Hooks run on the goroutine handling the event, including delivery goroutines. Keep them cheap and do not block in them.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub accepts subscriptions, verifies that subscribers meant to make them, and fans published content out to them.
It is an http.Handler serving one endpoint, the hub URL itself, which takes both subscription requests and publish notifications:
hub, err := websub.NewHub("https://example.com/hub")
mux.Handle("/hub", hub)
A Hub is safe for concurrent use.
func NewHub ¶
NewHub returns a Hub served at hubURL.
The URL is required because every content distribution request has to carry a rel=hub Link header pointing back here, and the hub cannot infer its own public URL from an incoming request in the general case.
func (*Hub) AddSniffer ¶
AddSniffer registers a callback that sees content published to topic before it is distributed. An empty topic sees everything.
Sniffers cannot change or block the content. They exist for hubs that also want to archive, index, or count what passes through.
func (*Hub) AddValidator ¶
func (h *Hub) AddValidator(fn func(*HubSubscription) (ok bool, reason string))
AddValidator registers a check run against every subscription request before the hub verifies it. Returning false denies the subscription, and the reason is passed on to the subscriber in the denial notification.
Validators run in the order they were added, and the first denial wins. Use them for the policy this package cannot know: which topics this hub serves, whether a callback host is allowed, rate limits.
func (*Hub) Publish ¶
Publish distributes content for topic to every subscriber.
With an empty body the hub fetches topic itself and uses whatever content type the response carries, which is the WebSub flow. With a body, that content is distributed as given.
Publish blocks until every delivery has been attempted and returns the failures joined together. Deliveries that fail are retried according to the hub's retry policy before being reported.
func (*Hub) ServeHTTP ¶
func (h *Hub) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP handles subscription requests and publish notifications. Both arrive as a POST to the hub URL and are told apart by hub.mode.
func (*Hub) Shutdown ¶
Shutdown waits for in-flight verification and delivery work to finish, or for ctx to end. It does not stop the HTTP server the hub is mounted on; stop that first, then call this.
func (*Hub) Subscriptions ¶
Subscriptions returns every subscription for topic.
type HubOption ¶
type HubOption interface {
// contains filtered or unexported methods
}
HubOption configures a Hub at construction.
func WithDeliveryConcurrency ¶
WithDeliveryConcurrency caps how many subscribers a hub delivers to at once during a single publish.
func WithDeliveryRetry ¶
WithDeliveryRetry sets how hard a hub tries to deliver content. attempts counts the first try, so 1 means no retry. The delay doubles after each failure, starting at backoff.
Be careful raising these. A hub retrying aggressively across many subscribers is a denial of service against itself.
func WithLeaseBounds ¶
WithLeaseBounds sets the range of lease durations a hub grants. Requests outside it are clamped rather than refused, which is what the spec has in mind when it says a hub may respect the requested value or not.
WebSub forbids perpetual leases, so a hub in ModeWebSub always grants something inside these bounds.
func WithSignatureAlgorithm ¶
func WithSignatureAlgorithm(alg SignatureAlgorithm) HubOption
WithSignatureAlgorithm sets the hash a hub signs content with. The default is DefaultSignatureAlgorithm. WebSub allows sha1 for compatibility with older subscribers but recommends against it.
func WithStore ¶
func WithStore(s SubscriptionStore) HubOption
WithStore sets where a hub keeps subscription state. The default is NewMemoryStore, which loses everything on restart. Use a backend from storage/* for anything that outlives a process.
type HubSubscription ¶
type HubSubscription struct {
Subscription
// CreatedAt is when the hub first verified this subscription.
CreatedAt time.Time
// LastDelivery is when the hub last attempted delivery, zero if never.
LastDelivery time.Time
// LastStatus is the HTTP status of the last delivery attempt, zero if
// the attempt failed before a response arrived.
LastStatus int
// Failures counts consecutive failed deliveries. It resets on success
// and drives retry backoff.
Failures int
}
HubSubscription is a subscription as tracked by a Hub, with the delivery bookkeeping a subscriber has no reason to carry.
type Link ¶
type Link struct {
// URL is the link target, resolved against the document it came from.
URL string
// Rels are the lowercased values of the rel parameter. RFC 8288 allows
// several in one parameter, separated by whitespace.
Rels []string
}
Link is one entry of an HTTP Link header.
https://www.rfc-editor.org/rfc/rfc8288
func ParseLinkHeader ¶
ParseLinkHeader parses the values of one or more Link headers. Relative targets are resolved against base when it is non-nil. Entries that are not parseable are skipped rather than failing the whole header, which is what RFC 8288 asks of consumers.
type Mode ¶
type Mode int
Mode selects which specification applies where WebSub and PubSubHubbub 0.4 differ. The public API is the same under either one.
const ( // ModeWebSub applies W3C WebSub semantics: leases are always finite and // verification is always asynchronous. // // https://www.w3.org/TR/websub/ ModeWebSub Mode = iota // ModePuSH04 applies PubSubHubbub 0.4 semantics: a subscription with no // lease is permanent, and synchronous verification is tolerated. // // https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html ModePuSH04 )
type Option ¶
type Option interface {
SubscriberOption
PublisherOption
HubOption
}
Option configures any of the three roles. Options that only make sense for one of them implement the narrower interface instead.
func WithHTTPClient ¶
WithHTTPClient sets the client used for outbound requests: discovery and subscription requests for a subscriber, publish notifications for a publisher, and content delivery for a hub. The default is a client with a 30 second timeout.
Give a hub a client with a generous connection pool. It fans one publish out to every subscriber at once.
func WithLogger ¶
WithLogger sets the logger. The default discards everything, so nothing is written unless you ask for it.
func WithMode ¶
WithMode selects the specification whose semantics apply. The default is ModeWebSub.
func WithUserAgent ¶
WithUserAgent sets the User-Agent sent on outbound requests. WebSub asks hubs to identify themselves as a hub, which the default does.
type Publisher ¶
type Publisher struct {
// contains filtered or unexported fields
}
Publisher advertises a topic's hub to discovering subscribers and tells that hub when the topic changes.
A publisher does not serve content itself. Your own handler does that; Publisher.Advertise gives you the headers it needs to carry, and Publisher.Middleware attaches them for you.
A Publisher is safe for concurrent use.
func NewPublisher ¶
func NewPublisher(baseURL string, opts ...PublisherOption) (*Publisher, error)
NewPublisher returns a Publisher for content served under baseURL.
Topic URLs passed to its methods may be relative, in which case they resolve against baseURL. That keeps the canonical URL in one place rather than repeated at every call site.
At least one hub is required, through WithHubs, since a publisher with no hub has nothing to advertise or notify.
func (*Publisher) Advertise ¶
Advertise returns the Link headers a response for topic must carry so a subscriber can discover where to subscribe: one rel=hub per configured hub, and exactly one rel=self holding the canonical topic URL.
func (*Publisher) Middleware ¶
Middleware returns a handler that adds the discovery headers for topic to everything next writes.
http.Handle("/feed", publisher.Middleware("/feed", feedHandler))
func (*Publisher) Publish ¶
Publish tells every configured hub that topic has new content.
With an empty body this is a ping: the hub fetches the topic itself, which is the mechanism WebSub describes and the one to prefer. With a body, the content is posted to the hub directly, which some hubs accept as an optimization and which PubSubHubbub 0.4 deployments use to publish resources the hub cannot reach.
Every hub is notified even if an earlier one fails. The returned error joins whatever went wrong, so errors.Is still works against it.
type PublisherOption ¶
type PublisherOption interface {
// contains filtered or unexported methods
}
PublisherOption configures a Publisher at construction.
func WithHubs ¶
func WithHubs(hubURLs ...string) PublisherOption
WithHubs sets the hubs a Publisher advertises and notifies. WebSub allows more than one, and expects a publisher that advertises several to notify all of them, which Publisher.Publish does.
type SignatureAlgorithm ¶
type SignatureAlgorithm string
SignatureAlgorithm names the hash a hub uses to sign distributed content. The wire format is the algorithm name, an equals sign, then the HMAC in lowercase hex.
const ( SHA1 SignatureAlgorithm = "sha1" SHA256 SignatureAlgorithm = "sha256" SHA384 SignatureAlgorithm = "sha384" SHA512 SignatureAlgorithm = "sha512" )
Algorithms WebSub permits. SHA1 exists for PubSubHubbub 0.4 hubs. It is accepted on verify but never chosen for signing.
func (SignatureAlgorithm) Valid ¶
func (a SignatureAlgorithm) Valid() bool
Valid reports whether a names an algorithm this package can compute.
type SubscribeOption ¶
type SubscribeOption interface {
// contains filtered or unexported methods
}
SubscribeOption configures a single call to Subscriber.Subscribe.
func WithHub ¶
func WithHub(hubURL string) SubscribeOption
WithHub subscribes at a known hub instead of discovering one. The topic URL is still used as given unless WithTopic overrides it.
func WithLease ¶
func WithLease(d time.Duration) SubscribeOption
WithLease requests a lease duration. Hubs are free to grant less, and the granted value is what lands in Subscription.Lease. Under ModePuSH04 a zero duration requests a permanent subscription; under ModeWebSub it falls back to DefaultLease.
func WithSecret ¶
func WithSecret(secret string) SubscribeOption
WithSecret sets the shared key the hub signs content with. Content that does not verify against it is dropped rather than delivered.
WebSub caps a secret at MaxSecretLength bytes, which Subscriber.Subscribe enforces, and says one should only be sent over an HTTPS callback, which it logs a warning about rather than refusing.
https://www.w3.org/TR/websub/#authenticated-content-distribution
func WithTopic ¶
func WithTopic(topicURL string) SubscribeOption
WithTopic sets the canonical topic URL to send as hub.topic, for the case where it differs from the URL passed to Subscribe and discovery is being skipped.
type Subscriber ¶
type Subscriber struct {
// contains filtered or unexported fields
}
Subscriber subscribes to topics and receives their content.
It is an http.Handler: mount it at the path prefix its callback URL points at, since each subscription gets its own callback below that prefix. With net/http that is a trailing slash pattern.
sub, err := websub.NewSubscriber("https://example.com/websub/")
mux.Handle("/websub/", sub)
A Subscriber is safe for concurrent use.
func NewSubscriber ¶
func NewSubscriber(callbackURL string, opts ...SubscriberOption) (*Subscriber, error)
NewSubscriber returns a Subscriber that receives content at callbackURL.
Each subscription appends an unguessable segment to callbackURL, so the URL given here is a prefix rather than the exact URL any single hub delivers to. It must be absolute, since it is sent to hubs verbatim.
func (*Subscriber) Await ¶
func (s *Subscriber) Await(ctx context.Context, sub *Subscription) (*Subscription, error)
Await blocks until the hub finishes verifying the pending request for sub, and returns the resulting state. It returns immediately for a subscription that is already settled.
A denied subscription returns a snapshot in state StateDenied alongside a *DenialError, which matches ErrSubscriptionDenied.
func (*Subscriber) Close ¶
func (s *Subscriber) Close() error
Close stops the renewal timers this subscriber holds. It does not unsubscribe anything: the subscriptions stay live at their hubs until their leases run out, which is what you want across a restart.
A Subscriber with automatic renewal on holds one timer per active subscription, so a long lived program that creates subscribers should close them. ServeHTTP keeps working afterward; only renewal stops.
func (*Subscriber) Discover ¶
Discover reports the hub and canonical topic that topicURL advertises, using this subscriber's HTTP client and mode.
func (*Subscriber) Forget ¶
func (s *Subscriber) Forget(sub *Subscription)
Forget drops a settled subscription from memory. Denied and unsubscribed subscriptions are kept so [Await] and [Subscriptions] can still report them; a long lived process that churns through subscriptions should call Forget once it no longer cares.
func (*Subscriber) OnNotify ¶
func (s *Subscriber) OnNotify(fn func(sub *Subscription, contentType string, body []byte))
OnNotify sets the handler for content distribution requests. It replaces any handler set earlier.
The handler runs on the goroutine serving the delivery, so the hub waits on it. Hand slow work to another goroutine. It is called only after the signature is verified, or not at all if the subscription has no secret.
func (*Subscriber) ServeHTTP ¶
func (s *Subscriber) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP handles both requests a hub makes to a callback URL: the GET that verifies intent or reports a denial, and the POST that delivers content.
func (*Subscriber) Subscribe ¶
func (s *Subscriber) Subscribe(ctx context.Context, topic string, opts ...SubscribeOption) (*Subscription, error)
Subscribe requests a subscription to topic.
Unless WithHub is given, topic is first fetched to discover its hub and canonical URL. Subscribe returns once the hub has accepted the request, which for WebSub is before the subscription is usable: the hub verifies intent out of band with a request that ServeHTTP answers. Use [Await] to wait for that to finish.
func (*Subscriber) Subscriptions ¶
func (s *Subscriber) Subscriptions() []Subscription
Subscriptions returns a snapshot of every subscription this subscriber knows about, including ones that were denied or have been unsubscribed.
func (*Subscriber) Unsubscribe ¶
func (s *Subscriber) Unsubscribe(ctx context.Context, sub *Subscription) error
Unsubscribe asks the hub to end sub. As with Subscribe, the hub verifies intent out of band, so the subscription is not gone when Unsubscribe returns. Use [Await] to wait for the hub to confirm.
type SubscriberOption ¶
type SubscriberOption interface {
// contains filtered or unexported methods
}
SubscriberOption configures a Subscriber at construction.
func WithRenewAt ¶
func WithRenewAt(fraction float64) SubscriberOption
WithRenewAt sets when a lease is renewed, as a fraction of the duration the hub granted. The default is DefaultRenewAt, so a one hour lease is renewed after 48 minutes, leaving room for a failed attempt to be retried before the subscription actually lapses.
Values outside the open interval (0, 1) are rejected: renewing at or after expiry is not renewal, and renewing immediately is a loop.
func WithoutAutoRenew ¶
func WithoutAutoRenew() SubscriberOption
WithoutAutoRenew stops a subscriber renewing its leases.
Renewal is on by default because WebSub leases are finite and hubs are required to enforce them, so a subscription that is never renewed just stops delivering, with nothing anywhere reporting that it did. Turn it off if your program manages subscription lifetime itself, or if it is not running continuously.
type Subscription ¶
type Subscription struct {
// Topic is the canonical topic URL, as resolved by discovery. This is
// the self URL the publisher advertises, which is not always the URL
// discovery started from.
Topic string
// Hub is the hub URL that serves Topic.
Hub string
// Callback is the subscriber URL the hub delivers to. It carries the
// per-subscription path segment the subscriber generates, so two
// subscriptions never share a callback.
Callback string
// Secret is the shared key used to sign content distribution requests.
// Empty means the subscription is unauthenticated. WebSub caps this at
// 200 bytes and requires HTTPS callbacks when it is set.
//
// https://www.w3.org/TR/websub/#authenticated-content-distribution
Secret string
// Lease is the duration the hub granted, which may be shorter than the
// duration requested. Zero means permanent, which only [ModePuSH04]
// allows.
Lease time.Duration
// ExpiresAt is when the lease elapses. Zero for permanent subscriptions.
ExpiresAt time.Time
// State is the handshake state at the time this snapshot was taken.
State SubscriptionState
}
Subscription is one subscriber's registration for one topic at one hub.
It is a snapshot, safe to copy and to hand to a SubscriptionStore. It is not updated in place as the handshake proceeds; use Subscriber.Await or [Subscriber.Lookup] to read current state.
type SubscriptionState ¶
type SubscriptionState int
SubscriptionState tracks a subscription through the verification of intent handshake.
https://www.w3.org/TR/websub/#verifying-intent
const ( // StatePending means the hub accepted the request but has not yet // verified intent. WebSub hubs answer with 202 Accepted and verify out // of band, so this is the state Subscribe normally returns in. StatePending SubscriptionState = iota // StateActive means the hub verified intent and the subscription is // receiving content. StateActive // StateDenied means the hub refused the subscription, either in its // response or with a later denial notification to the callback. StateDenied // StateExpired means the lease elapsed without renewal. StateExpired // StateUnsubscribed means the subscription was torn down, either by the // subscriber or by the hub. StateUnsubscribed )
func (SubscriptionState) String ¶
func (s SubscriptionState) String() string
String implements fmt.Stringer.
type SubscriptionStore ¶
type SubscriptionStore interface {
// Save writes sub, replacing any existing entry with the same topic and
// callback. Re-subscribing to an active subscription is legal in both
// specs and lands here as an update, not an error.
Save(ctx context.Context, sub Subscription) error
// Get returns the subscription for topic and callback. It returns an
// error wrapping [ErrUnknownSubscription] if there is none.
Get(ctx context.Context, topic, callback string) (Subscription, error)
// Delete removes the subscription for topic and callback. Deleting one
// that does not exist is not an error, since unsubscribe is idempotent.
Delete(ctx context.Context, topic, callback string) error
// ListByTopic returns every subscription for topic, in no particular
// order.
ListByTopic(ctx context.Context, topic string) ([]Subscription, error)
// Topics returns every topic with at least one subscription, in no
// particular order.
Topics(ctx context.Context) ([]string, error)
// ListExpiring returns every subscription whose lease elapses before
// the given time, in no particular order. Permanent subscriptions are
// never returned. Hubs use it to sweep dead subscriptions; subscribers
// use it to renew leases ahead of expiry.
ListExpiring(ctx context.Context, before time.Time) ([]Subscription, error)
}
SubscriptionStore holds subscription state for a Hub, and for a Subscriber that needs its subscriptions to survive a restart.
Implementations must be safe for concurrent use. A hub calls Save on every verified subscription and ListByTopic on every publish, so ListByTopic is the method worth indexing for.
Backends in storage/* implement this interface. To write your own, run it against the shared suite in storage/storetest.
func NewMemoryStore ¶
func NewMemoryStore() SubscriptionStore
NewMemoryStore returns an in-memory SubscriptionStore. It is the default for NewHub and is intended for development, tests, and single-process hubs that can afford to lose state on restart.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
adapter
|
|
|
echo
module
|
|
|
fiber
module
|
|
|
gin
module
|
|
|
storage
|
|
|
postgres
module
|
|
|
redis
module
|
|
|
sqlite
module
|
|
|
storetest
module
|
|
|
tests
|
|
|
conformance
Package conformance holds the WebSub and PubSubHubbub 0.4 conformance cases for this package, written so they can be replayed against a websub handler mounted behind any router.
|
Package conformance holds the WebSub and PubSubHubbub 0.4 conformance cases for this package, written so they can be replayed against a websub handler mounted behind any router. |
|
websubtest
module
|