Documentation
¶
Overview ¶
Package gateway turns a GoAkt cluster into a single, cluster-aware ingress tier for HTTP, WebSocket, and Server-Sent Events (SSE) traffic - the role an nginx/caddy front-tier normally plays in front of a stateless HTTP fleet.
Two design boundaries drive every type in this package:
- Plain HTTP requests are served by ordinary net/http handlers with zero actor involvement. Actorizing a request/response cycle that lives for a few milliseconds is pure overhead; nothing in this package puts one in the way of a short-lived HTTP handler.
- Only long-lived WebSocket/SSE connections get an addressable identity. Each accepted connection is registered in a local Registry and, so that it can be addressed from any node in the cluster, backed by a lightweight ephemeral actor (relocation disabled, long-lived passivation) whose sole job is to relay a delivery to the socket it owns.
Message delivery to a connection is two-tier: Registry.SendToConnection checks the local connection table first and, on a hit, writes the socket directly with no actor or cluster machinery involved. Only on a miss does it fall back to a cluster-wide actor lookup (actor.ActorSystem.ActorOf) and a remote Tell to the node that owns the connection. Registry.Broadcast follows the same shape for topic fan-out: local subscribers are written directly, and remote nodes are reached through a small pub/sub bridge built entirely on GoAkt's public actor.Subscribe/actor.Unsubscribe API (see bridge.go) - this package has no dependency on any GoAkt internal package.
The TLS side of the package (CertIssuer, CertStore, Manager) lets every node in the cluster terminate TLS for any hosted domain from one certificate, issued exactly once cluster-wide: issuance is arbitrated through a storage-agnostic Coordinator this library owns (see coordinator.go), the resulting certificate is distributed to every node through the same Coordinator, and renewal is driven by GoAkt's cluster-single-fire cron schedule (actor.ActorSystem.ScheduleWithCron) so only one node re-issues per renewal window. See Manager, CertIssuer, CertStore, and Coordinator.
Index ¶
- Variables
- func NewRSACertificateRequest(bits int) func(domain string) (csrPEM, keyPEM []byte, err error)
- type AuthenticatedOriginPulls
- type CertIssuer
- type CertStore
- type Certificate
- type CloudflareOriginCAIssuer
- type Coordinator
- type Drainer
- type Manager
- func (m *Manager) EnsureCertificate(ctx context.Context, domain string) (*tls.Certificate, error)
- func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
- func (m *Manager) Start(ctx context.Context) error
- func (m *Manager) Stop(ctx context.Context) error
- func (m *Manager) TLSConfig() *tls.Config
- type ManagerOption
- func WithAllowedDomains(domains ...string) ManagerOption
- func WithCertIssuer(issuer CertIssuer) ManagerOption
- func WithCertStore(store CertStore) ManagerOption
- func WithCoordinator(coordinator Coordinator) ManagerOption
- func WithIssuanceLockTTL(d time.Duration) ManagerOption
- func WithRenewBefore(d time.Duration) ManagerOption
- func WithRenewInterval(cronExpression string) ManagerOption
- type MemoryCertStore
- type MemoryCoordinator
- func (m *MemoryCoordinator) Get(_ context.Context, key string) ([]byte, bool, error)
- func (m *MemoryCoordinator) Put(_ context.Context, key string, value []byte, ttl time.Duration) error
- func (m *MemoryCoordinator) TryLock(_ context.Context, key string, ttl time.Duration) (func(context.Context) error, error)
- type Registry
- func (r *Registry) Broadcast(ctx context.Context, topic string, payload []byte) error
- func (r *Registry) Has(id string) bool
- func (r *Registry) Join(_ context.Context, id, topic string) error
- func (r *Registry) Leave(_ context.Context, id, topic string) error
- func (r *Registry) Len() int
- func (r *Registry) Register(ctx context.Context, id string, send func([]byte) error, topics ...string) error
- func (r *Registry) SendToConnection(ctx context.Context, id string, payload []byte) error
- func (r *Registry) Unregister(ctx context.Context, id string) error
- type SSEHandler
- type SSEHandlerOption
- func WithSSEAuthFunc(f func(*http.Request) error) SSEHandlerOption
- func WithSSEIDFunc(f func(*http.Request) string) SSEHandlerOption
- func WithSSEKeepAlive(d time.Duration) SSEHandlerOption
- func WithSSELogger(logger log.Logger) SSEHandlerOption
- func WithSSEOnConnect(f func(ctx context.Context, id string, r *http.Request)) SSEHandlerOption
- func WithSSEOnDisconnect(f func(id string)) SSEHandlerOption
- func WithSSESendBuffer(size int) SSEHandlerOption
- func WithSSETopics(f func(*http.Request) []string) SSEHandlerOption
- type Server
- type ServerOption
- type StaticIssuer
- type WSHandler
- type WSHandlerOption
- func WithWSAuthFunc(f func(*http.Request) error) WSHandlerOption
- func WithWSIDFunc(f func(*http.Request) string) WSHandlerOption
- func WithWSLogger(logger log.Logger) WSHandlerOption
- func WithWSOnConnect(f func(ctx context.Context, id string, r *http.Request)) WSHandlerOption
- func WithWSOnDisconnect(f func(id string)) WSHandlerOption
- func WithWSOnMessage(f func(ctx context.Context, id string, payload []byte)) WSHandlerOption
- func WithWSSendBuffer(size int) WSHandlerOption
- func WithWSTopics(f func(*http.Request) []string) WSHandlerOption
Constants ¶
This section is empty.
Variables ¶
var ( // ErrConnectionNotFound is returned by Registry.SendToConnection when the target // connection id is not registered locally and cannot be located anywhere else in // the cluster either. ErrConnectionNotFound = errors.New("gateway: connection not found") // ErrConnectionExists is returned by Registry.Register when the given connection id // is already registered. ErrConnectionExists = errors.New("gateway: connection already registered") // ErrConnectionClosed is returned when writing to a connection that has already // been unregistered/closed. ErrConnectionClosed = errors.New("gateway: connection closed") // ErrBackpressure is returned when a connection's outbound buffer is full and the // write could not be queued without blocking. ErrBackpressure = errors.New("gateway: connection send buffer full") // AuthFunc rejects the incoming request. ErrUnauthorized = errors.New("gateway: unauthorized") // system was not started with pub/sub enabled (see actor.ActorSystem.TopicActor). ErrPubSubUnavailable = errors.New("gateway: pub/sub is not enabled on this actor system") // ErrDomainNotAllowed is returned by Manager.GetCertificate and EnsureCertificate // when the requested SNI/domain is not covered by the configured allow list. ErrDomainNotAllowed = errors.New("gateway: domain not allowed") // ErrNoIssuer is returned by Manager when certificate issuance is required but no // CertIssuer was configured. ErrNoIssuer = errors.New("gateway: no certificate issuer configured") // ErrIssuanceTimeout is returned when this node lost the coordinator-arbitrated // issuance race for a domain and the winning node did not publish a certificate // before the wait deadline elapsed. ErrIssuanceTimeout = errors.New("gateway: timed out waiting for coordinated certificate issuance") // ErrCertificateNotFound is returned by CertStore.Get when no certificate is stored // for the given domain. ErrCertificateNotFound = errors.New("gateway: certificate not found") // ErrOriginPullVerificationFailed is returned when Authenticated Origin Pulls is // enabled and the inbound client certificate does not chain to the configured CA. ErrOriginPullVerificationFailed = errors.New("gateway: origin pull client certificate verification failed") // ErrLockNotAcquired is returned by Coordinator.TryLock when the lock is already // held by someone else. ErrLockNotAcquired = errors.New("gateway: lock not acquired") // ErrIssuanceLockExpired is returned by Manager when a CertIssuer call outlives the // configured issuance lock TTL (see WithIssuanceLockTTL). The Coordinator lock has no // renewal/heartbeat, so a slow issuer can let the lock expire and a second process // acquire it while the first is still mid-issuance; returning this error instead of // silently caching the result makes that window visible rather than quietly risking a // duplicate CertIssuer call. Configure a WithIssuanceLockTTL comfortably longer than // your CertIssuer's worst-case latency to avoid it. ErrIssuanceLockExpired = errors.New("gateway: certificate issuance took longer than the issuance lock ttl") )
Functions ¶
func NewRSACertificateRequest ¶
NewRSACertificateRequest builds a PEM-encoded PKCS#10 certificate signing request and matching RSA private key for domain, suitable for use as CloudflareOriginCAIssuer.RequestCertificate. It uses only the standard library, so it adds no dependency beyond what go.mod already carries.
Types ¶
type AuthenticatedOriginPulls ¶
type AuthenticatedOriginPulls struct {
// CAPEM is the PEM-encoded CA certificate (or bundle) that inbound client
// certificates must chain to.
CAPEM []byte
}
AuthenticatedOriginPulls configures mutual TLS verification of inbound connections against a trusted CA - the shape Cloudflare's "Authenticated Origin Pulls" feature requires of an origin server: only connections presenting a client certificate that chains to Cloudflare's published origin-pull CA are accepted.
The CA is supplied by the caller (CAPEM) rather than embedded, since a real deployment should track Cloudflare's currently published origin-pull CA bundle rather than trust a copy vendored into this library.
func (*AuthenticatedOriginPulls) Apply ¶
func (a *AuthenticatedOriginPulls) Apply(cfg *tls.Config) error
Apply overlays mTLS verification onto cfg: it sets ClientAuth to tls.RequireAndVerifyClientCert and ClientCAs to a pool built from CAPEM. It returns an error if CAPEM does not contain a valid PEM-encoded certificate.
func (*AuthenticatedOriginPulls) Verify ¶
func (a *AuthenticatedOriginPulls) Verify(state tls.ConnectionState) error
Verify checks that the peer certificates presented in state chain to the configured CA. tls.Config.ClientAuth = tls.RequireAndVerifyClientCert already performs this verification during the handshake itself; Verify exists for callers that want to re-check an already-established connection's state explicitly (e.g. in an HTTP handler, via *http.Request.TLS) rather than solely trust the handshake outcome.
type CertIssuer ¶
type CertIssuer interface {
// Issue returns a new Certificate for domain. It is called by Manager both for the
// first issuance of a domain and for renewal ahead of expiry.
Issue(ctx context.Context, domain string) (*Certificate, error)
}
CertIssuer obtains a certificate for a domain. Manager arbitrates calls to Issue so that, when using a cluster-shared Coordinator, at most one node calls it for a given domain at a time (see Manager.EnsureCertificate).
Implementations must be safe for concurrent use.
type CertStore ¶
type CertStore interface {
// Get returns the certificate stored for domain. It returns ErrCertificateNotFound
// if none is stored.
Get(ctx context.Context, domain string) (*Certificate, error)
// Put stores cert, overwriting any previous certificate for the same domain.
Put(ctx context.Context, cert *Certificate) error
// Delete removes the certificate stored for domain, if any.
Delete(ctx context.Context, domain string) error
}
CertStore persists issued certificates so that a cold start does not have to re-issue one for every hosted domain. Manager always keeps a copy in the configured Coordinator for fast, shared SNI lookups; a CertStore is the layer behind that - by default an in-memory MemoryCertStore, but applications facing a full restart of every node should plug in a persistent implementation (e.g. backed by a database or object storage) to avoid depending on the issuer being reachable/willing to re-issue on every cold start.
Implementations must be safe for concurrent use.
type Certificate ¶
type Certificate struct {
// Domain is the domain the certificate was issued for.
Domain string
// CertPEM is the PEM-encoded leaf certificate (and, where applicable, intermediate
// chain) as returned by the issuer.
CertPEM []byte
// KeyPEM is the PEM-encoded private key matching CertPEM.
KeyPEM []byte
// NotAfter is the certificate's expiry, used by Manager to decide when to renew.
NotAfter time.Time
}
Certificate is the PEM-encoded issuance result CertIssuer.Issue returns. It is what Manager stores in a CertStore and hands to tls.Config.GetCertificate after parsing.
type CloudflareOriginCAIssuer ¶
type CloudflareOriginCAIssuer struct {
// APIToken authenticates against the Origin CA API. Origin CA calls use an API
// Token/Key dedicated to Origin CA rather than a general Cloudflare API token; see
// the Cloudflare documentation for how to obtain one.
APIToken string
// BaseURL overrides the Cloudflare API base URL. Defaults to
// "https://api.cloudflare.com/client/v4" when empty; tests point this at a local
// httptest.Server.
BaseURL string
// RequestedValidityDays is the requested certificate lifetime in days, one of the
// values Cloudflare's API accepts (7, 30, 90, 365, 730, 1095, or 5475). Defaults to
// 365 when zero.
RequestedValidityDays int
// RequestCertificate builds the PEM-encoded PKCS#10 certificate signing request and
// matching private key for domain. It is a function rather than a fixed
// implementation so callers can plug in their preferred key type/size; see
// NewRSACertificateRequest for a ready-made RSA implementation.
RequestCertificate func(domain string) (csrPEM, keyPEM []byte, err error)
// HTTPClient performs the API request. Defaults to http.DefaultClient when nil.
HTTPClient *http.Client
}
CloudflareOriginCAIssuer issues certificates through the Cloudflare Origin CA REST API (https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/), using only net/http - no Cloudflare SDK dependency. It signs a certificate request created locally (CertRequestPEM) and returns whatever Cloudflare hands back.
Cloudflare-issued origin certificates are trusted by Cloudflare's edge but not by public clients, which is the intended shape for a service that only ever receives traffic proxied through Cloudflare.
func (*CloudflareOriginCAIssuer) Issue ¶
func (c *CloudflareOriginCAIssuer) Issue(ctx context.Context, domain string) (*Certificate, error)
Issue implements CertIssuer by calling the Cloudflare Origin CA "create certificate" endpoint (POST /certificates).
type Coordinator ¶
type Coordinator interface {
// Get returns the value stored for key. ok is false if key is absent or has
// expired; a missing key is not an error.
Get(ctx context.Context, key string) (value []byte, ok bool, err error)
// Put stores value under key. A ttl of zero or less means the value never expires
// on its own (it is overwritten or explicitly replaced instead).
Put(ctx context.Context, key string, value []byte, ttl time.Duration) error
// TryLock attempts to acquire an exclusive, TTL-bounded lock on key. On success it
// returns an unlock function that releases the lock; unlock is safe to call more
// than once and safe to call after ttl has already elapsed (a no-op in that case,
// since the lock is already gone). On failure it returns ErrLockNotAcquired.
//
// The lock is a real mutual exclusion primitive: implementations must guarantee
// that at most one caller holds a given key at a time, and that a caller's unlock
// never releases a lock some other caller has since acquired (e.g. after the
// original holder's TTL expired).
TryLock(ctx context.Context, key string, ttl time.Duration) (unlock func(context.Context) error, err error)
}
Coordinator is the storage-agnostic backend Manager uses to arbitrate certificate issuance and distribute the result across every process that shares it. It is owned by this library rather than by GoAkt: GoAkt's cluster KV store is PA/EC with last-write-wins semantics and documents its own lock as "recommended for efficiency, not correctness", which is the wrong substrate for a certificate issuance lock - a duplicated issuance burns CA rate limit.
A Coordinator does not have to be cluster-shared: NewMemoryCoordinator (the default) is process-local and gives Manager the same single-flight-only behavior a non-clustered deployment needs. Share a Coordinator implementation across processes (e.g. coordinator/redis) to get cluster-wide arbitration and distribution.
Implementations must be safe for concurrent use.
type Drainer ¶
type Drainer interface {
Drain()
}
Drainer is implemented by connection handlers (WSHandler, SSEHandler) that hold long-lived connections http.Server.Shutdown cannot evict on its own: Shutdown ignores hijacked WebSocket sockets entirely and would wait on open SSE streams until its context expired.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager terminates TLS for one or more domains from certificates shared across every process that shares its Coordinator: issuance for a given domain is arbitrated through Coordinator.TryLock so at most one caller invokes the configured CertIssuer, the result is distributed through Coordinator.Put/Get, and a cron renewal schedule (single-fire cluster-wide by GoAkt scheduler design when running in a GoAkt cluster) drives renewal ahead of expiry.
The issuance lock is held for at most WithIssuanceLockTTL and is not renewed/extended while a CertIssuer call is outstanding: if issuance takes longer than lockTTL, the lock can expire mid-issuance, letting a second process acquire it and also call the issuer. doEnsure detects this after the fact and returns ErrIssuanceLockExpired instead of caching the result, but the extra CertIssuer call itself is not prevented. Set WithIssuanceLockTTL comfortably longer than your CertIssuer's worst-case latency.
Without an explicit WithCoordinator, Manager defaults to a process-local MemoryCoordinator: issuance is still deduplicated (so concurrent handshakes for a cold domain only call the issuer once per process) but nothing is shared across processes. Share a cluster-backed Coordinator (e.g. coordinator/redis) to get cluster-wide arbitration and distribution.
A Manager is safe for concurrent use. Use NewManager to construct one.
func NewManager ¶
func NewManager(system actor.ActorSystem, logger log.Logger, opts ...ManagerOption) *Manager
NewManager creates a Manager backed by system. Call Start before serving traffic so the renewal schedule is registered, and Stop during shutdown to release it.
func (*Manager) EnsureCertificate ¶
EnsureCertificate returns a ready-to-serve certificate for domain, issuing (or fetching from another caller's issuance) one if necessary. Concurrent calls for the same domain on this process are deduplicated; concurrent calls across every process that shares this Manager's Coordinator are arbitrated so only one of them calls the configured CertIssuer (see Manager documentation).
It returns ErrDomainNotAllowed if WithAllowedDomains was set and domain is not in it, and ErrNoIssuer if issuance is required but no CertIssuer was configured.
func (*Manager) GetCertificate ¶
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate implements the tls.Config.GetCertificate signature for SNI-based dynamic certificate lookup: assign it directly, e.g. tls.Config{GetCertificate: manager.GetCertificate}.
func (*Manager) Start ¶
Start registers the renewal schedule (cluster-single-fire when system is running in a GoAkt cluster). It is a no-op if renewal was disabled via WithRenewInterval("").
type ManagerOption ¶
type ManagerOption func(*Manager)
ManagerOption configures a Manager created with NewManager.
func WithAllowedDomains ¶
func WithAllowedDomains(domains ...string) ManagerOption
WithAllowedDomains restricts issuance/serving to the given domains. Without this option, any domain requested via GetCertificate/EnsureCertificate is allowed.
func WithCertIssuer ¶
func WithCertIssuer(issuer CertIssuer) ManagerOption
WithCertIssuer sets the CertIssuer used to obtain certificates that are not already cached or stored. Without one, Manager can only serve certificates that a CertStore already has (e.g. pre-populated via CertStore.Put).
func WithCertStore ¶
func WithCertStore(store CertStore) ManagerOption
WithCertStore overrides the persistent CertStore layer. Defaults to a MemoryCertStore.
func WithCoordinator ¶
func WithCoordinator(coordinator Coordinator) ManagerOption
WithCoordinator overrides the Coordinator Manager uses to arbitrate issuance and distribute certificates. Defaults to a process-local NewMemoryCoordinator; pass a cluster-shared implementation (e.g. coordinator/redis) to coordinate issuance across every process that shares it.
func WithIssuanceLockTTL ¶
func WithIssuanceLockTTL(d time.Duration) ManagerOption
WithIssuanceLockTTL sets how long the coordinated issuance lock for a domain is held for. Defaults to 2 minutes. The lock is not renewed while the CertIssuer call is in flight, so set this comfortably longer than your CertIssuer's worst-case latency: if issuance overruns it, doEnsure returns ErrIssuanceLockExpired instead of silently caching a result whose single-issuer guarantee is no longer certain.
func WithRenewBefore ¶
func WithRenewBefore(d time.Duration) ManagerOption
WithRenewBefore sets how far ahead of a certificate's expiry Manager renews it. Defaults to 30 days.
func WithRenewInterval ¶
func WithRenewInterval(cronExpression string) ManagerOption
WithRenewInterval sets the cron expression (go-quartz syntax, 6 fields with seconds first) the renewal schedule uses to check for expiring certificates. Defaults to hourly ("0 0 * * * *"). Pass an empty string to disable automatic renewal entirely (EnsureCertificate/GetCertificate will still re-issue on-demand once a served certificate has actually expired).
type MemoryCertStore ¶
type MemoryCertStore struct {
// contains filtered or unexported fields
}
MemoryCertStore is the default, in-process CertStore. It does not survive a process restart, which is why Manager also always keeps a copy in the configured Coordinator: on a single node restart the certificate is fetched back from there, and on a full cold start (no Coordinator, or a fresh one) it is re-issued.
func NewMemoryCertStore ¶
func NewMemoryCertStore() *MemoryCertStore
NewMemoryCertStore creates an empty MemoryCertStore.
func (*MemoryCertStore) Delete ¶
func (m *MemoryCertStore) Delete(_ context.Context, domain string) error
Delete implements CertStore.
func (*MemoryCertStore) Get ¶
func (m *MemoryCertStore) Get(_ context.Context, domain string) (*Certificate, error)
Get implements CertStore.
func (*MemoryCertStore) Put ¶
func (m *MemoryCertStore) Put(_ context.Context, cert *Certificate) error
Put implements CertStore.
type MemoryCoordinator ¶
type MemoryCoordinator struct {
// contains filtered or unexported fields
}
MemoryCoordinator is the default, in-process Coordinator. It is appropriate for a single-node deployment, local development, and tests: TryLock/Put/Get only coordinate callers within this process, so it gives Manager exactly the "issue once per process" behavior a non-clustered gateway needs. Share a cluster-backed Coordinator (e.g. coordinator/redis) across processes to get cluster-wide arbitration.
func NewMemoryCoordinator ¶
func NewMemoryCoordinator() *MemoryCoordinator
NewMemoryCoordinator creates an empty MemoryCoordinator.
func (*MemoryCoordinator) Put ¶
func (m *MemoryCoordinator) Put(_ context.Context, key string, value []byte, ttl time.Duration) error
Put implements Coordinator.
func (*MemoryCoordinator) TryLock ¶
func (m *MemoryCoordinator) TryLock(_ context.Context, key string, ttl time.Duration) (func(context.Context) error, error)
TryLock implements Coordinator. Ownership is tracked with a per-acquisition token so that an unlock call can never release a lock some other caller has since acquired after this one's ttl elapsed.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the local, per-node table of registered WebSocket/SSE connections. It is the "local connection table" the two-tier delivery model described in the gateway package documentation is built around: SendToConnection and Broadcast always prefer a direct write to a locally held connection over any actor/cluster machinery, and only fall back to cluster addressing when the target is not held by this node.
A Registry is safe for concurrent use.
func NewRegistry ¶
func NewRegistry(system actor.ActorSystem, logger log.Logger) *Registry
NewRegistry creates a Registry backed by system. system is used to spawn the per-connection ephemeral actors (relocation disabled, long-lived passivation) that make registered connections addressable from other nodes, and to bridge topic broadcasts across the cluster (see bridge.go).
func (*Registry) Broadcast ¶
Broadcast delivers payload to every connection joined to topic across the cluster. Local members are written to directly; remote members are reached through the topic bridge (see bridge.go).
func (*Registry) Join ¶
Join adds an already-registered connection to a topic's local membership, creating the cluster fan-out bridge for that topic on first use. It returns ErrConnectionNotFound if id is not registered.
func (*Registry) Leave ¶
Leave removes an already-registered connection from a topic's local membership, tearing down the cluster fan-out bridge for that topic once its last local member leaves.
func (*Registry) Register ¶
func (r *Registry) Register(ctx context.Context, id string, send func([]byte) error, topics ...string) error
Register adds a new connection to the local table and makes it addressable cluster-wide under connActorName(id). send is invoked (from any goroutine, including remote-delivery ones) whenever a payload must be written to the underlying socket; it must be non-blocking or perform its own internal buffering, since Registry never queues on the caller's behalf.
The id is reserved in the table before the backing actor is spawned, so a concurrent Register for the same id fails immediately with ErrConnectionExists rather than racing past the reservation. If an Unregister for id arrives while the actor is still spawning, Register rolls back the spawn and returns ErrConnectionClosed instead of resurrecting a connection the Unregister caller already believes is gone.
It returns ErrConnectionExists if id is already registered.
func (*Registry) SendToConnection ¶
SendToConnection delivers payload to the connection identified by id anywhere in the cluster. If id is registered on this node, payload is written directly to the socket with no actor or cluster machinery involved. Otherwise, SendToConnection resolves the connection's owning node through the cluster-aware actor directory (ActorSystem.ActorOf) and delivers payload there.
It returns ErrConnectionNotFound if id is not registered anywhere in the cluster.
func (*Registry) Unregister ¶
Unregister removes a connection from the local table, leaves every topic it had joined, and shuts down its backing actor. It is a no-op if id is not registered.
If id is currently reserved by an in-flight Register (its actor is still spawning), Unregister marks the reservation dead and returns immediately: the in-flight Register observes this and rolls back the spawn itself, since it is the only side that can safely stop the actor it just created.
type SSEHandler ¶
type SSEHandler struct {
// contains filtered or unexported fields
}
SSEHandler opens a Server-Sent Events stream for every incoming request and registers each one in its Registry for the lifetime of the connection. SSE is one-way (server to client); inbound application data, if any, belongs in an ordinary HTTP endpoint the client posts to separately.
func NewSSEHandler ¶
func NewSSEHandler(registry *Registry, opts ...SSEHandlerOption) *SSEHandler
NewSSEHandler returns an SSEHandler bound to registry. The returned handler is an http.Handler; wire its Drain method into Server via WithDrainOnShutdown (or call it from your own shutdown path) so open streams terminate promptly on shutdown.
func (*SSEHandler) Drain ¶
func (h *SSEHandler) Drain()
Drain terminates every open SSE stream and makes new requests fail fast with 503, so a graceful server shutdown is not held hostage by long-lived streams. Safe to call more than once.
func (*SSEHandler) ServeHTTP ¶
func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
type SSEHandlerOption ¶
type SSEHandlerOption func(*SSEHandler)
SSEHandlerOption configures an http.Handler created with NewSSEHandler.
func WithSSEAuthFunc ¶
func WithSSEAuthFunc(f func(*http.Request) error) SSEHandlerOption
WithSSEAuthFunc sets the auth hook run before a connection is accepted and registered. A non-nil error rejects the request with HTTP 403 Forbidden.
func WithSSEIDFunc ¶
func WithSSEIDFunc(f func(*http.Request) string) SSEHandlerOption
WithSSEIDFunc sets the function used to derive a connection's id from the request. Without one, a random UUID is generated per connection.
func WithSSEKeepAlive ¶
func WithSSEKeepAlive(d time.Duration) SSEHandlerOption
WithSSEKeepAlive sets the interval at which a comment-only keepalive event is sent to detect dead connections and prevent idle-timing-out intermediate proxies. Defaults to 15 seconds.
func WithSSELogger ¶
func WithSSELogger(logger log.Logger) SSEHandlerOption
WithSSELogger sets the logger used to report connection-handling errors. Defaults to log.DiscardLogger.
func WithSSEOnConnect ¶
WithSSEOnConnect sets the callback invoked once a connection has been registered and the response stream has been opened.
func WithSSEOnDisconnect ¶
func WithSSEOnDisconnect(f func(id string)) SSEHandlerOption
WithSSEOnDisconnect sets the callback invoked once a connection has been unregistered.
func WithSSESendBuffer ¶
func WithSSESendBuffer(size int) SSEHandlerOption
WithSSESendBuffer sets the size of each connection's outbound buffer. When full, Registry.SendToConnection/Broadcast deliveries to that connection return ErrBackpressure instead of blocking. Defaults to 256.
func WithSSETopics ¶
func WithSSETopics(f func(*http.Request) []string) SSEHandlerOption
WithSSETopics sets the function used to derive the topics a connection should be joined to at registration time.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a thin convenience wrapper around http.Server that wires a Manager's cluster-shared, SNI-dynamic TLS certificates (and, optionally, Authenticated Origin Pulls mTLS verification) into the standard library's TLS listener. Plain HTTP handlers, WebSocket upgrades (NewWSHandler), and SSE streams (NewSSEHandler) are all just http.Handler values mounted on Server like on any other *http.Server - Server adds nothing actor- or cluster-related to the request path itself.
Using Server is entirely optional: any application already managing its own *http.Server can instead set TLSConfig: manager.TLSConfig() directly.
func (*Server) ListenAndServe ¶
ListenAndServe starts the Manager's renewal schedule (if any) and serves traffic, terminating TLS through it when WithTLSManager was set, or serving plain HTTP otherwise. It blocks until the server stops (see Shutdown) and returns http.ErrServerClosed on a clean shutdown.
func (*Server) Shutdown ¶
Shutdown gracefully stops the server: it first drains handlers registered via WithDrainOnShutdown (evicting long-lived WebSocket/SSE connections that http.Server.Shutdown cannot terminate itself), then stops the Manager's renewal schedule if any, and finally shuts the HTTP listener down.
type ServerOption ¶
type ServerOption func(*Server)
ServerOption configures a Server created with NewServer.
func WithAuthenticatedOriginPulls ¶
func WithAuthenticatedOriginPulls(pulls *AuthenticatedOriginPulls) ServerOption
WithAuthenticatedOriginPulls enables mTLS verification of inbound connections against pulls's configured CA (see AuthenticatedOriginPulls), rejecting any connection that does not present a valid client certificate. Requires WithTLSManager.
func WithDrainOnShutdown ¶
func WithDrainOnShutdown(drainers ...Drainer) ServerOption
WithDrainOnShutdown registers connection handlers whose Drain method Server.Shutdown invokes before shutting the HTTP listener down, so long-lived WebSocket/SSE connections are evicted promptly and clients reconnect to surviving replicas during rolling deploys.
func WithReadHeaderTimeout ¶
func WithReadHeaderTimeout(d time.Duration) ServerOption
WithReadHeaderTimeout sets the underlying http.Server's ReadHeaderTimeout.
func WithTLSManager ¶
func WithTLSManager(manager *Manager) ServerOption
WithTLSManager configures Server to terminate TLS using manager's cluster-shared, SNI-dynamic certificates. Without this option, Server serves plain HTTP.
type StaticIssuer ¶
type StaticIssuer struct {
// contains filtered or unexported fields
}
StaticIssuer is a CertIssuer that serves a fixed, pre-provisioned certificate for one or more domains, e.g. loaded from files at startup. It never renews: Certificate.NotAfter reflects whatever was supplied, and Manager's renewal schedule will simply keep re-"issuing" (returning) the same static material.
func NewStaticIssuer ¶
func NewStaticIssuer(cert *Certificate, domains ...string) *StaticIssuer
NewStaticIssuer creates a StaticIssuer that serves cert for every domain in domains.
func (*StaticIssuer) Issue ¶
func (s *StaticIssuer) Issue(_ context.Context, domain string) (*Certificate, error)
Issue implements CertIssuer.
type WSHandler ¶
type WSHandler struct {
// contains filtered or unexported fields
}
WSHandler upgrades incoming requests to WebSocket connections and registers each one in its Registry for the lifetime of the socket. It implements http.Handler; mount it only on the path(s) meant to accept WebSocket upgrades - plain (non-upgrade) HTTP handling is entirely out of scope here by design.
func NewWSHandler ¶
func NewWSHandler(registry *Registry, opts ...WSHandlerOption) *WSHandler
NewWSHandler returns a WSHandler bound to registry. The returned handler is an http.Handler; wire its Drain method into Server via WithDrainOnShutdown (or call it from your own shutdown path) so established sockets are evicted on shutdown.
type WSHandlerOption ¶
type WSHandlerOption func(*WSHandler)
WSHandlerOption configures a WebSocket http.Handler created with NewWSHandler.
func WithWSAuthFunc ¶
func WithWSAuthFunc(f func(*http.Request) error) WSHandlerOption
WithWSAuthFunc sets the auth hook run during the WebSocket handshake, before the connection is accepted or registered. A non-nil error rejects the upgrade with HTTP 403 Forbidden.
func WithWSIDFunc ¶
func WithWSIDFunc(f func(*http.Request) string) WSHandlerOption
WithWSIDFunc sets the function used to derive a connection's id from the upgrade request. Without one, a random UUID is generated per connection.
func WithWSLogger ¶
func WithWSLogger(logger log.Logger) WSHandlerOption
WithWSLogger sets the logger used to report connection-handling errors. Defaults to log.DiscardLogger.
func WithWSOnConnect ¶
WithWSOnConnect sets the callback invoked once a connection has been registered and is ready to receive deliveries.
func WithWSOnDisconnect ¶
func WithWSOnDisconnect(f func(id string)) WSHandlerOption
WithWSOnDisconnect sets the callback invoked once a connection has been unregistered.
func WithWSOnMessage ¶
func WithWSOnMessage(f func(ctx context.Context, id string, payload []byte)) WSHandlerOption
WithWSOnMessage sets the callback invoked for every text/binary message received from the client.
func WithWSSendBuffer ¶
func WithWSSendBuffer(size int) WSHandlerOption
WithWSSendBuffer sets the size of each connection's outbound buffer. When full, Registry.SendToConnection/Broadcast deliveries to that connection return ErrBackpressure instead of blocking. Defaults to 256.
func WithWSTopics ¶
func WithWSTopics(f func(*http.Request) []string) WSHandlerOption
WithWSTopics sets the function used to derive the topics a connection should be joined to at registration time, from the upgrade request (e.g. a room/channel query parameter or claim in an auth token).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
coordinator
|
|
|
conformance
Package conformance is a shared test suite every gateway.Coordinator implementation must pass, so gateway.MemoryCoordinator and coordinator/redis.Coordinator (and any third-party implementation) are held to the exact same contract.
|
Package conformance is a shared test suite every gateway.Coordinator implementation must pass, so gateway.MemoryCoordinator and coordinator/redis.Coordinator (and any third-party implementation) are held to the exact same contract. |
|
redis
Package redis provides a Redis-backed gateway.Coordinator, for sharing certificate issuance arbitration and distribution across every process in a deployment.
|
Package redis provides a Redis-backed gateway.Coordinator, for sharing certificate issuance arbitration and distribution across every process in a deployment. |
|
examples
|
|
|
echo
command
Command echo is a minimal, runnable demonstration of the gateway package:
|
Command echo is a minimal, runnable demonstration of the gateway package: |