Documentation
¶
Overview ¶
Package azureservicebus provides a local, in-memory emulation of Azure Service Bus's Brokered Messaging REST API: queue and topic/subscription CRUD, and the full send/peek-lock/complete/abandon/dead-letter message lifecycle over HTTP+SharedAccessSignature auth, plus an AMQP 1.0 plane (pkgs/amqp1) over the same backend for the official SDKs, including sessions over AMQP. See PARITY.md for scope and known gaps.
Index ¶
- Constants
- Variables
- func SignSAS(resource string, expiry int64, keyValue string) string
- func VerifySAS(auth Authorization, keyValue string, now time.Time) bool
- type AMQPBackend
- type Authorization
- type ConfigProvider
- type EntityConfig
- type EntityRef
- type Handler
- func (h *Handler) ExtractOperation(c *echo.Context) string
- func (h *Handler) ExtractResource(c *echo.Context) string
- func (h *Handler) GetSupportedOperations() []string
- func (h *Handler) Handler() echo.HandlerFunc
- func (h *Handler) MatchPriority() int
- func (h *Handler) Name() string
- func (h *Handler) Reset()
- func (h *Handler) Restore(ctx context.Context, data []byte) error
- func (h *Handler) RouteMatcher() service.Matcher
- func (h *Handler) ServeAMQP(ln net.Listener, useTLS bool) error
- func (h *Handler) Shutdown(ctx context.Context)
- func (h *Handler) Snapshot(ctx context.Context) []byte
- func (h *Handler) StartWorker(ctx context.Context) error
- func (h *Handler) WithJanitor(interval time.Duration) *Handler
- func (h *Handler) WithSASValidation(key string) *Handler
- type InMemoryBackend
- func (b *InMemoryBackend) Abandon(ref EntityRef, deadLetter bool, messageID, lockToken string) error
- func (b *InMemoryBackend) AcceptSessionWait(ctx context.Context, ref EntityRef, sessionID, owner string, ...) (SessionGrant, error)
- func (b *InMemoryBackend) CancelScheduled(ref EntityRef, seqs []int64) error
- func (b *InMemoryBackend) CheckSessionLock(ref EntityRef, sessionID, owner string) error
- func (b *InMemoryBackend) Complete(ref EntityRef, deadLetter bool, messageID, lockToken string) error
- func (b *InMemoryBackend) CreateQueue(name string, cfg ...EntityConfig) (bool, error)
- func (b *InMemoryBackend) CreateSubscription(topic, name string, cfg ...EntityConfig) (bool, error)
- func (b *InMemoryBackend) CreateTopic(name string, cfg ...EntityConfig) (bool, error)
- func (b *InMemoryBackend) DeadLetter(ref EntityRef, messageID, lockToken, reason, description string) error
- func (b *InMemoryBackend) Defer(ref EntityRef, messageID, lockToken string) error
- func (b *InMemoryBackend) DeleteQueue(name string) error
- func (b *InMemoryBackend) DeleteSubscription(topic, name string) error
- func (b *InMemoryBackend) DeleteTopic(name string) error
- func (b *InMemoryBackend) FindByLockToken(ref EntityRef, token string) (MessageInfo, bool, error)
- func (b *InMemoryBackend) GetQueueInfo(name string) (QueueInfo, error)
- func (b *InMemoryBackend) GetSessionState(ref EntityRef, sessionID, owner string) ([]byte, error)
- func (b *InMemoryBackend) GetSubscriptionInfo(topic, name string) (SubscriptionInfo, error)
- func (b *InMemoryBackend) GetTopicInfo(name string) (TopicInfo, error)
- func (b *InMemoryBackend) ListQueues() []QueueInfo
- func (b *InMemoryBackend) ListSubscriptions(topic string) ([]SubscriptionInfo, error)
- func (b *InMemoryBackend) ListTopics() []TopicInfo
- func (b *InMemoryBackend) Peek(ref EntityRef, deadLetter bool, fromSeq int64, maxCount int) ([]MessageInfo, error)
- func (b *InMemoryBackend) PeekLock(ref EntityRef, deadLetter bool, lockDuration time.Duration) (MessageInfo, error)
- func (b *InMemoryBackend) PeekLockSessionWait(ctx context.Context, ref EntityRef, sessionID, owner string, ...) (MessageInfo, error)
- func (b *InMemoryBackend) PeekLockWait(ctx context.Context, ref EntityRef, deadLetter bool, ...) (MessageInfo, error)
- func (b *InMemoryBackend) PeekSession(ref EntityRef, sessionID string, fromSeq int64, maxCount int) ([]MessageInfo, error)
- func (b *InMemoryBackend) QueueExists(name string) bool
- func (b *InMemoryBackend) ReceiveAndDelete(ref EntityRef, deadLetter bool) (MessageInfo, error)
- func (b *InMemoryBackend) ReceiveDeferred(ref EntityRef, seqs []int64, lock bool) ([]MessageInfo, error)
- func (b *InMemoryBackend) ReleaseSession(ref EntityRef, sessionID, owner string)
- func (b *InMemoryBackend) RenewLock(ref EntityRef, lockToken string) (time.Time, error)
- func (b *InMemoryBackend) RenewSessionLock(ref EntityRef, sessionID, owner string) (time.Time, error)
- func (b *InMemoryBackend) RequiresSession(ref EntityRef) (bool, error)
- func (b *InMemoryBackend) Reset()
- func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error
- func (b *InMemoryBackend) Send(ref EntityRef, msg NewMessage) (MessageInfo, error)
- func (b *InMemoryBackend) SessionOf(ref EntityRef, owner string) (string, bool)
- func (b *InMemoryBackend) SetSessionState(ref EntityRef, sessionID, owner string, state []byte) error
- func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte
- func (b *InMemoryBackend) SubscriptionExists(topic, name string) bool
- func (b *InMemoryBackend) TopicExists(name string) bool
- type Janitor
- type MessageInfo
- type NewMessage
- type Provider
- type QueueInfo
- type SessionBackend
- type SessionGrant
- type Settings
- type StorageBackend
- type SubscriptionInfo
- type TopicInfo
Constants ¶
const ( DefaultAMQPPort = 5672 DefaultAMQPTLSPort = 5671 )
Default AMQP 1.0 ports (the IANA assignments Service Bus uses).
const ( ReasonMaxDeliveryCountExceeded = "MaxDeliveryCountExceeded" ReasonTTLExpired = "TTLExpiredException" )
Auto dead-letter reasons real Service Bus stamps on system dead-lettering.
const ( // DefaultLockDuration is applied to a PeekLock when the caller specifies // no timeout query parameter. DefaultLockDuration = 60 * time.Second // DefaultMessageTTL is applied to Send when the caller supplies no // TimeToLive in BrokerProperties. DefaultMessageTTL = 14 * 24 * time.Hour // MaxDeliveryCount is how many times a message may be // peek-locked-and-abandoned before it is automatically moved to its // entity's dead-letter sub-queue, matching real Service Bus's own // default MaxDeliveryCount. MaxDeliveryCount = 10 // MaxLockDuration is real Service Bus's documented maximum LockDuration // for a queue or subscription (its default is 1 minute; 5 minutes is the // upper bound a CreateQueue/CreateSubscription request may configure -- // see https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-transfers-locks-settlement#peeklock, // which documents "the maximum value is 5 minutes"). handler.go's // validateEntityConfig rejects a create request specifying more than // this with 400 Bad Request, matching real Service Bus's own behavior. MaxLockDuration = 5 * time.Minute )
Default and bound values, matching real Service Bus's documented defaults.
const DefaultKeyName = "RootManageSharedAccessKey"
DefaultKeyName is gopherstack's fixed root shared-access-policy key name, mirroring the real Service Bus default policy name created on every new namespace.
const DefaultKeyValue = "2R1W2VORtFi9HrmRQ1Gxp7xbySq7W0FAs2BvTZDdXeo="
DefaultKeyValue is gopherstack's fixed, publicly published development SAS key (base64-encoded), analogous to pkgs/azureauth.DefaultAccountKey / Azurite's devstoreaccount1 key: a fixed dev secret so azure-sdk-for-go's default connection-string shape works unmodified out of the box, and so WithSASValidation has something deterministic to check against by default.
const DefaultNamespace = "sbemulatorns"
DefaultNamespace is gopherstack's fixed, Azurite-style default Service Bus namespace name. Real Service Bus namespaces are addressed as "<namespace>.servicebus.windows.net"; gopherstack has no DNS story for that, so this constant exists purely so a connection string built from it (Endpoint=sb://<DefaultNamespace>.servicebus.windows.net/;SharedAccessKeyName=...) has a plausible, stable shape for documentation/examples. The actual listener is addressed by host:port (see settings.go's DefaultPort), not by this name.
const DefaultPort = 10003
DefaultPort is Azure Service Bus's fixed, gopherstack-chosen TCP port. This follows the same pattern as services/azureblob's DefaultPort (10000), services/azurequeue's DefaultPort (10001), and services/azuretable's DefaultPort (10002): pick one default and try to bind exactly that, rather than drawing from cli.go's shared --port-range-start/--port-range-end PortAlloc pool. Unlike those three, there is no Azurite Service Bus emulator to mirror -- Azurite doesn't implement Service Bus -- so 10003 is simply the next available slot after Table's 10002 in gopherstack's own numbering convention. See AZURE.md section 9 (M5) for the full rationale.
const MaxPeekLockWaitTimeout = 30 * time.Second
MaxPeekLockWaitTimeout is the ceiling PeekLock's long-poll "?timeout=" query parameter is clamped to. Real Service Bus documents 30 seconds as its own maximum long-poll/operation timeout, and gopherstack matches that value here as a deliberate cap on server-side resource use per in-flight long-poll request (one goroutine and one open connection held for up to this long). The wait is bounded by this cap plus request-context cancellation on client disconnect -- NOT by any http.Server timeout: azureServiceBusReadTimeout only bounds reading the request itself (headers/body), not handler execution time, and this server sets no WriteTimeout or handler deadline, so a long-poll handler blocking well past 60s would not be torn down by the server. See PARITY.md.
const (
// MaxSessionStateBytes is Service Bus's documented session state limit.
MaxSessionStateBytes = 256 * 1024
)
Variables ¶
var ( ErrQueueNotFound = errors.New("azureservicebus: queue not found") ErrQueueAlreadyExists = errors.New("azureservicebus: queue already exists") ErrTopicNotFound = errors.New("azureservicebus: topic not found") ErrTopicAlreadyExists = errors.New("azureservicebus: topic already exists") ErrSubscriptionNotFound = errors.New("azureservicebus: subscription not found") ErrSubscriptionExists = errors.New("azureservicebus: subscription already exists") ErrMessageNotFound = errors.New("azureservicebus: message not found") ErrLockTokenMismatch = errors.New("azureservicebus: lock token mismatch") ErrMessageNotLocked = errors.New("azureservicebus: message is not locked") ErrInvalidEntityRef = errors.New("azureservicebus: invalid entity reference") ErrInvalidEntityConfig = errors.New("azureservicebus: invalid entity configuration") ErrDeadLetterSource = errors.New("azureservicebus: cannot dead-letter a dead-lettered message") ErrSessionIDRequired = errors.New("the SessionId was not set on a message, and it cannot be sent to the entity. " + "Entities that have session support enabled can only receive messages that have the SessionId set to a valid value") ErrSessionRequired = errors.New("it is not possible for an entity that requires sessions " + "to create a non-sessionful message receiver") ErrNotSessionEntity = errors.New("it is not possible to create a session receiver " + "on an entity that does not require sessions") ErrSessionCannotBeLocked = errors.New("the requested session cannot be accepted: it is locked by another receiver") ErrSessionLockLost = errors.New("the session lock has expired or was lost") ErrNoSessionAvailable = errors.New("no session is available") // ErrSnapshotNull* are returned by Restore when a snapshot map/slice holds // a JSON null entry, which decodes to a nil pointer that would panic on // first dereference if stored as-is. Mirrors services/azurequeue's // identical family of snapshot-validation errors. ErrSnapshotQueueNull = errors.New("azureservicebus: restore snapshot: queue is null") ErrSnapshotTopicNull = errors.New("azureservicebus: restore snapshot: topic is null") ErrSnapshotSubscriptionNull = errors.New("azureservicebus: restore snapshot: subscription is null") ErrSnapshotMessageNull = errors.New("azureservicebus: restore snapshot: message is null") )
Sentinel errors for Azure Service Bus operations.
var ErrBadPath = errors.New("azureservicebus: unrecognized path")
ErrBadPath is returned by parseRequestPath for a path that doesn't match any recognized Service Bus REST shape.
var ErrInvalidISO8601Duration = errors.New("azureservicebus: invalid ISO 8601 duration")
ErrInvalidISO8601Duration is returned by parseISO8601Duration for a string that doesn't match the supported PnDTnHnMnS/PTnHnMnS subset.
var ErrNilAppContext = errors.New("azureservicebus: nil app context")
ErrNilAppContext is returned when Init is called with a nil AppContext.
Functions ¶
func SignSAS ¶
SignSAS computes the Service Bus SAS signature for resource+expiry using keyValue (base64-encoded), matching the real algorithm: HMAC-SHA256 over "<url-encoded resource>\n<expiry>", base64-encoded.
func VerifySAS ¶
func VerifySAS(auth Authorization, keyValue string, now time.Time) bool
VerifySAS cryptographically verifies auth against keyValue, additionally rejecting an expired token (se in the past relative to now). Callers only invoke this when opting into validation (see Handler.checkAuth / WithSASValidation) -- structural parsing always accepts the header regardless of what this returns.
Types ¶
type AMQPBackend ¶
type AMQPBackend interface {
StorageBackend
SessionBackend
Defer(ref EntityRef, messageID, lockToken string) error
ReceiveDeferred(ref EntityRef, seqs []int64, lock bool) ([]MessageInfo, error)
RenewLock(ref EntityRef, lockToken string) (time.Time, error)
CancelScheduled(ref EntityRef, seqs []int64) error
FindByLockToken(ref EntityRef, lockToken string) (MessageInfo, bool, error)
}
AMQPBackend is the backend surface the AMQP plane needs on top of the REST StorageBackend.
type Authorization ¶
type Authorization struct {
Resource string // sr: URL-encoded resource URI the token authorizes
Signature string // sig: base64 HMAC-SHA256
KeyName string // skn: shared-access-policy key name
Expiry int64 // se: unix seconds the token expires at
}
Authorization holds the structurally-parsed fields of a Service Bus "Authorization: SharedAccessSignature sr=<resource>&sig=<hmac>&se=<expiry>&skn=<keyname>" header. Parsing never fails on a bad/missing signature -- only on a header that isn't shaped like a SAS token at all -- mirroring pkgs/azureauth's "structural parse always succeeds, verification is opt-in" philosophy (see services/s3's PresignSecret and Blob/Queue/Table's WithSharedKeyValidation).
func ParseSASAuthorization ¶
func ParseSASAuthorization(header string) (Authorization, bool)
ParseSASAuthorization structurally parses a Service Bus SAS Authorization header value. It always extracts whatever key=value pairs are present; ok is false only when the header is missing the scheme prefix entirely.
type ConfigProvider ¶
type ConfigProvider interface {
GetAzureServiceBusSettings() Settings
}
ConfigProvider is a private interface to extract AzureServiceBus configuration from the abstract AppContext Config, mirroring services/azurequeue.ConfigProvider.
type EntityConfig ¶
type EntityConfig struct {
LockDuration time.Duration
DefaultMessageTTL time.Duration
MaxDeliveryCount int
RequiresSession bool
}
EntityConfig holds the per-entity configuration properties real Service Bus accepts on CreateQueue/CreateTopic/CreateSubscription (LockDuration, MaxDeliveryCount, DefaultMessageTimeToLive), parsed from the Atom+XML create-request body (see atom.go). Which fields are meaningful depends on the entity kind: a queue honors all three; a topic honors only DefaultMessageTTL (applied as the message-TTL cap at Send time, since a topic never itself holds messages); a subscription honors LockDuration and MaxDeliveryCount. A zero-valued field falls back to the corresponding package-level default (DefaultLockDuration/MaxDeliveryCount/ DefaultMessageTTL) -- see the lockDuration/maxDeliveryCount/ defaultMessageTTL accessor methods.
type EntityRef ¶
EntityRef identifies a brokered-messaging entity that can hold messages: either a queue, or one specific subscription of a topic (a topic itself never holds messages directly -- sending to a topic fans the message out to every one of its subscriptions' own message lists). Exactly one of Queue or (Topic, Subscription) is set.
type Handler ¶
type Handler struct {
Backend StorageBackend
AMQPCert *tls.Certificate
Endpoint string
SASKeyValue string
Port int
AMQPPort int
AMQPTLSPort int
ValidateSAS bool
// contains filtered or unexported fields
}
Handler is the Echo HTTP handler for Azure Service Bus operations.
func NewHandler ¶
func NewHandler(backend StorageBackend) *Handler
NewHandler creates a new Azure Service Bus Handler. Port defaults to DefaultPort; callers (typically provider.go) override it from Settings.
func (*Handler) ExtractOperation ¶
ExtractOperation extracts the Azure Service Bus operation name from the request, for metrics labeling.
func (*Handler) ExtractResource ¶
ExtractResource extracts the entity resource identifier from the request path, for metrics labeling.
func (*Handler) GetSupportedOperations ¶
GetSupportedOperations returns the list of supported Azure Service Bus operations.
func (*Handler) Handler ¶
func (h *Handler) Handler() echo.HandlerFunc
Handler returns the Echo handler function for Azure Service Bus operations.
func (*Handler) MatchPriority ¶
MatchPriority returns the routing priority for the AzureServiceBus handler. Irrelevant in practice since RouteMatcher never matches.
func (*Handler) Reset ¶
func (h *Handler) Reset()
Reset clears all in-memory state. It is used by the POST /_gopherstack/reset endpoint for CI pipelines and rapid local development.
func (*Handler) RouteMatcher ¶
RouteMatcher exists only to satisfy service.Registerable's interface contract: AzureServiceBus never matches on the shared AWS single-port Router (see Provider's doc comment).
func (*Handler) ServeAMQP ¶
ServeAMQP serves AMQP 1.0 on ln (TLS-wrapped when useTLS) until Shutdown.
func (*Handler) Shutdown ¶
Shutdown stops the dedicated Service Bus listener. Mirrors services/azurequeue's Shutdown.
func (*Handler) Snapshot ¶
Snapshot implements persistence.Persistable by delegating to the backend.
func (*Handler) StartWorker ¶
StartWorker binds the dedicated Service Bus listener, starts serving on it, and -- if WithJanitor was called -- starts the background lock-expiry/dead-letter sweep. Mirrors services/azurequeue's StartWorker exactly.
func (*Handler) WithJanitor ¶
WithJanitor attaches a background lock-expiry/dead-letter janitor to the handler, mirroring services/azurequeue's WithJanitor. If Backend is not an *InMemoryBackend the call is a no-op.
func (*Handler) WithSASValidation ¶
WithSASValidation enables cryptographic verification of SAS (SharedAccessSignature) Authorization headers, checking each signature against the given key (base64-encoded). A blank key defaults to DefaultKeyValue. Mirrors services/s3's WithPresignValidation and Blob/Queue/Table's WithSharedKeyValidation: when never called, SAS headers are parsed structurally only, never cryptographically rejected.
type InMemoryBackend ¶
type InMemoryBackend struct {
// contains filtered or unexported fields
}
InMemoryBackend implements StorageBackend using in-memory maps guarded by a single RWMutex. Shaped after services/azurequeue's InMemoryBackend.
func NewInMemoryBackend ¶
func NewInMemoryBackend() *InMemoryBackend
NewInMemoryBackend creates a new empty InMemoryBackend.
func (*InMemoryBackend) Abandon ¶
func (b *InMemoryBackend) Abandon(ref EntityRef, deadLetter bool, messageID, lockToken string) error
Abandon releases a locked message's lock (making it immediately available again), after verifying lockToken matches. If DeliveryCount has reached ref's configured MaxDeliveryCount, the message is moved to the entity's dead-letter sub-queue instead of being made available, matching real Service Bus's automatic dead-lettering on delivery-count exhaustion. Either outcome -- released back to the live list, or moved to the dead-letter sub-queue -- makes a message newly visible somewhere on ref, so both wake any PeekLockWait waiter on ref (the same notify channel is shared by the live and dead-letter lists; a waiter on the list that didn't change simply re-checks and goes back to sleep, a harmless spurious wakeup).
func (*InMemoryBackend) AcceptSessionWait ¶
func (b *InMemoryBackend) AcceptSessionWait( ctx context.Context, ref EntityRef, sessionID, owner string, timeout time.Duration, ) (SessionGrant, error)
AcceptSessionWait locks sessionID for owner. An empty sessionID selects the next session holding deliverable messages, waiting up to timeout for one; ErrNoSessionAvailable is returned when none appears. A specific session held by another owner fails at once with ErrSessionCannotBeLocked.
func (*InMemoryBackend) CancelScheduled ¶
func (b *InMemoryBackend) CancelScheduled(ref EntityRef, seqs []int64) error
CancelScheduled removes still-pending scheduled messages. For a topic ref every subscription copy of the schedule is removed. Any unknown sequence number yields ErrMessageNotFound after the known ones were cancelled.
func (*InMemoryBackend) CheckSessionLock ¶
func (b *InMemoryBackend) CheckSessionLock(ref EntityRef, sessionID, owner string) error
CheckSessionLock returns ErrSessionLockLost unless owner holds sessionID.
func (*InMemoryBackend) Complete ¶
func (b *InMemoryBackend) Complete(ref EntityRef, deadLetter bool, messageID, lockToken string) error
Complete permanently removes a locked message identified by messageID from ref (or its dead-letter sub-queue), after verifying lockToken matches.
func (*InMemoryBackend) CreateQueue ¶
func (b *InMemoryBackend) CreateQueue(name string, cfg ...EntityConfig) (bool, error)
CreateQueue creates a new, empty queue with the given configuration (see EntityConfig; cfg is variadic so pre-existing callers passing only a name keep compiling -- see the StorageBackend interface's doc comment). If a queue with the same name already exists, created is false, err is nil, and its existing configuration is left untouched (idempotent, mirroring services/azurequeue's CreateQueue). ErrQueueAlreadyExists is reserved for a future strict-create variant.
func (*InMemoryBackend) CreateSubscription ¶
func (b *InMemoryBackend) CreateSubscription(topic, name string, cfg ...EntityConfig) (bool, error)
CreateSubscription creates a new, empty subscription of topic with the given configuration (see EntityConfig -- LockDuration and MaxDeliveryCount are meaningful for a subscription; cfg is variadic so pre-existing callers passing only topic/name keep compiling). If a subscription with the same name already exists on that topic, created is false and err is nil (idempotent, mirroring CreateQueue/CreateTopic). Returns ErrTopicNotFound if the topic does not exist.
Real Service Bus subscription creation accepts an optional SQL filter rule in the request body; this MVP deliberately does not parse or store it -- every subscription behaves as if it had Service Bus's default "match all" rule (TrueFilter). See PARITY.md's filter-evaluation gap and handler.go's createSubscription, which discards the request body's filter rule after extracting any EntityConfig properties it also carries.
func (*InMemoryBackend) CreateTopic ¶
func (b *InMemoryBackend) CreateTopic(name string, cfg ...EntityConfig) (bool, error)
CreateTopic creates a new, empty topic (no subscriptions) with the given configuration (see EntityConfig -- only DefaultMessageTTL is meaningful for a topic; cfg is variadic so pre-existing callers passing only a name keep compiling). If a topic with the same name already exists, created is false and err is nil, mirroring CreateQueue's idempotent-retry semantics.
func (*InMemoryBackend) DeadLetter ¶
func (b *InMemoryBackend) DeadLetter(ref EntityRef, messageID, lockToken, reason, description string) error
DeadLetter implements StorageBackend.DeadLetter.
func (*InMemoryBackend) Defer ¶
func (b *InMemoryBackend) Defer(ref EntityRef, messageID, lockToken string) error
Defer parks a locked message; it is then retrievable only by sequence number via ReceiveDeferred.
func (*InMemoryBackend) DeleteQueue ¶
func (b *InMemoryBackend) DeleteQueue(name string) error
DeleteQueue removes a queue and all of its messages (including any dead-lettered ones). Returns ErrQueueNotFound if the queue does not exist.
func (*InMemoryBackend) DeleteSubscription ¶
func (b *InMemoryBackend) DeleteSubscription(topic, name string) error
DeleteSubscription removes a subscription and all of its messages (including any dead-lettered ones). Returns ErrTopicNotFound or ErrSubscriptionNotFound as appropriate.
func (*InMemoryBackend) DeleteTopic ¶
func (b *InMemoryBackend) DeleteTopic(name string) error
DeleteTopic removes a topic, all of its subscriptions, and every message held by those subscriptions. Returns ErrTopicNotFound if the topic does not exist.
func (*InMemoryBackend) FindByLockToken ¶
func (b *InMemoryBackend) FindByLockToken(ref EntityRef, token string) (MessageInfo, bool, error)
FindByLockToken returns the locked message holding token and whether it sits in the dead-letter sub-queue.
func (*InMemoryBackend) GetQueueInfo ¶
func (b *InMemoryBackend) GetQueueInfo(name string) (QueueInfo, error)
GetQueueInfo returns name's metadata. Returns ErrQueueNotFound if it does not exist.
func (*InMemoryBackend) GetSessionState ¶
func (b *InMemoryBackend) GetSessionState(ref EntityRef, sessionID, owner string) ([]byte, error)
GetSessionState returns the session's state, nil when unset.
func (*InMemoryBackend) GetSubscriptionInfo ¶
func (b *InMemoryBackend) GetSubscriptionInfo(topic, name string) (SubscriptionInfo, error)
GetSubscriptionInfo returns name's metadata within topic. Returns ErrTopicNotFound or ErrSubscriptionNotFound as appropriate.
func (*InMemoryBackend) GetTopicInfo ¶
func (b *InMemoryBackend) GetTopicInfo(name string) (TopicInfo, error)
GetTopicInfo returns name's metadata. Returns ErrTopicNotFound if it does not exist.
func (*InMemoryBackend) ListQueues ¶
func (b *InMemoryBackend) ListQueues() []QueueInfo
ListQueues returns every queue's metadata, sorted by name.
func (*InMemoryBackend) ListSubscriptions ¶
func (b *InMemoryBackend) ListSubscriptions(topic string) ([]SubscriptionInfo, error)
ListSubscriptions returns every subscription of topic's metadata, sorted by name. Returns ErrTopicNotFound if topic does not exist.
func (*InMemoryBackend) ListTopics ¶
func (b *InMemoryBackend) ListTopics() []TopicInfo
ListTopics returns every topic's metadata, sorted by name.
func (*InMemoryBackend) Peek ¶
func (b *InMemoryBackend) Peek(ref EntityRef, deadLetter bool, fromSeq int64, maxCount int) ([]MessageInfo, error)
Peek implements StorageBackend.Peek.
func (*InMemoryBackend) PeekLock ¶
func (b *InMemoryBackend) PeekLock(ref EntityRef, deadLetter bool, lockDuration time.Duration) (MessageInfo, error)
PeekLock returns the oldest visible (unlocked, non-expired) message on ref -- or its dead-letter sub-queue, if deadLetter is true -- locking it for lockDuration and incrementing its delivery count. Returns ErrMessageNotFound if none is available. See the StorageBackend doc comment for lockDuration <= 0's fallback behavior.
func (*InMemoryBackend) PeekLockSessionWait ¶
func (b *InMemoryBackend) PeekLockSessionWait( ctx context.Context, ref EntityRef, sessionID, owner string, timeout time.Duration, ) (MessageInfo, error)
PeekLockSessionWait locks the oldest deliverable message of the session owner holds, waiting up to timeout. ErrSessionLockLost means the session lock expired or moved.
func (*InMemoryBackend) PeekLockWait ¶
func (b *InMemoryBackend) PeekLockWait( ctx context.Context, ref EntityRef, deadLetter bool, lockDuration, timeout time.Duration, ) (MessageInfo, error)
PeekLockWait is PeekLock's long-poll variant. See the StorageBackend doc comment. Mirrors services/sqs's ReceiveMessage/pollReceive/receiveOnce long-poll shape (a broadcast notify channel plus a 1-second recheck-timer backstop), adapted to take a context so a disconnecting client releases the waiting goroutine immediately -- a deliberate improvement over the SQS precedent, which has no ctx parameter (see PARITY.md). Deadline/backstop timing uses the real wall clock (time.Now/time.Timer), not the backend's mockable nowFunc -- matching services/sqs's identical choice and making this method exercisable under testing/synctest's fake clock.
func (*InMemoryBackend) PeekSession ¶
func (b *InMemoryBackend) PeekSession( ref EntityRef, sessionID string, fromSeq int64, maxCount int, ) ([]MessageInfo, error)
PeekSession is Peek restricted to one session.
func (*InMemoryBackend) QueueExists ¶
func (b *InMemoryBackend) QueueExists(name string) bool
QueueExists reports whether a queue named name exists.
func (*InMemoryBackend) ReceiveAndDelete ¶
func (b *InMemoryBackend) ReceiveAndDelete(ref EntityRef, deadLetter bool) (MessageInfo, error)
ReceiveAndDelete implements StorageBackend.ReceiveAndDelete.
func (*InMemoryBackend) ReceiveDeferred ¶
func (b *InMemoryBackend) ReceiveDeferred(ref EntityRef, seqs []int64, lock bool) ([]MessageInfo, error)
ReceiveDeferred returns deferred messages by sequence number, locking them (lock=true) or removing them (lock=false). Unknown sequence numbers are skipped.
func (*InMemoryBackend) ReleaseSession ¶
func (b *InMemoryBackend) ReleaseSession(ref EntityRef, sessionID, owner string)
ReleaseSession drops owner's lock on sessionID, if it still holds it.
func (*InMemoryBackend) RenewLock ¶
RenewLock extends the lock identified by lockToken and returns the new expiry. ErrMessageNotFound means the lock was lost.
func (*InMemoryBackend) RenewSessionLock ¶
func (b *InMemoryBackend) RenewSessionLock(ref EntityRef, sessionID, owner string) (time.Time, error)
RenewSessionLock extends owner's lock on sessionID and returns its expiry.
func (*InMemoryBackend) RequiresSession ¶
func (b *InMemoryBackend) RequiresSession(ref EntityRef) (bool, error)
RequiresSession reports whether the addressed queue or subscription is session-enabled.
func (*InMemoryBackend) Reset ¶
func (b *InMemoryBackend) Reset()
Reset clears all in-memory state. It is used by the POST /_gopherstack/reset endpoint for CI pipelines and rapid local development.
func (*InMemoryBackend) Restore ¶
func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error
Restore loads backend state from a JSON snapshot. It implements persistence.Persistable.
func (*InMemoryBackend) Send ¶
func (b *InMemoryBackend) Send(ref EntityRef, msg NewMessage) (MessageInfo, error)
Send enqueues msg on ref. See the StorageBackend.Send doc comment for the queue-vs-topic fan-out distinction and the per-entity TTL cap.
func (*InMemoryBackend) SessionOf ¶
func (b *InMemoryBackend) SessionOf(ref EntityRef, owner string) (string, bool)
SessionOf returns the session owner currently holds on ref.
func (*InMemoryBackend) SetSessionState ¶
func (b *InMemoryBackend) SetSessionState(ref EntityRef, sessionID, owner string, state []byte) error
SetSessionState stores the session's state; nil clears it.
func (*InMemoryBackend) Snapshot ¶
func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte
Snapshot serialises the backend state to JSON. It implements persistence.Persistable.
func (*InMemoryBackend) SubscriptionExists ¶
func (b *InMemoryBackend) SubscriptionExists(topic, name string) bool
SubscriptionExists reports whether topic has a subscription named name. Returns false (not an error) if topic itself does not exist.
func (*InMemoryBackend) TopicExists ¶
func (b *InMemoryBackend) TopicExists(name string) bool
TopicExists reports whether a topic named name exists.
type Janitor ¶
type Janitor struct {
Backend *InMemoryBackend
Interval time.Duration
}
Janitor is the Azure Service Bus background worker responsible for the two things real Service Bus does automatically over time: releasing a peek-locked message whose lock has expired without being Completed or Abandoned (making it available for redelivery, or dead-lettering it if that expiry pushed its delivery count past MaxDeliveryCount), and moving a TTL-expired message to its entity's dead-letter sub-queue. Mirrors services/azurequeue's Janitor/TTL-sweep shape.
func NewJanitor ¶
func NewJanitor(backend *InMemoryBackend, interval time.Duration) *Janitor
NewJanitor creates a new Azure Service Bus Janitor for the given backend.
type MessageInfo ¶
type MessageInfo struct {
EnqueuedTime time.Time
LockedUntil time.Time
ExpiresAt time.Time
ScheduledFor time.Time
CustomHeaders map[string]string
AppProps map[string]any
ReplyToSessionID string
MessageID string
SessionID string
To string
ContentType string
CorrelationID string
Label string
ReplyTo string
DeadLetterErrorDescription string
LockToken string
DeadLetterReason string
Body []byte
SequenceNumber int64
DeliveryCount int64
Deferred bool
}
MessageInfo is the public, read-only snapshot of a stored message returned by Send/PeekLock. LockToken/LockedUntil are populated only for a peek-locked read; Send returns them zero.
type NewMessage ¶
type NewMessage struct {
CustomHeaders map[string]string
ContentType string
Label string
CorrelationID string
MessageID string
ReplyTo string
SessionID string
To string
// ReplyToSessionID, AppProps and ScheduledFor are populated by the AMQP
// plane; ScheduledFor in the future holds the message back until then.
ReplyToSessionID string
AppProps map[string]any
ScheduledFor time.Time
Body []byte
TimeToLive time.Duration
}
NewMessage is the input shape for sending a message, built from the incoming request's body and BrokerProperties header (see message_ops.go).
type Provider ¶
type Provider struct{}
Provider implements service.Provider for the Azure Service Bus service.
Like services/azureblob/azurequeue/azuretable, AzureServiceBus does not register a RouteMatcher into the shared AWS single-port Router: its path shape (/<queue-or-topic>[/subscriptions/<name>][/messages[/...]]) has no service-identifying header the way AWS's X-Amz-Target does, and multiplexing it onto a shared port risks exactly the collision the router avoids by construction for AWS services (see AZURE.md section 4). Instead the returned Handler implements service.BackgroundWorker and stands up its own dedicated *echo.Echo/*http.Server, listening on a fixed port (DefaultPort, 10003). It is registered in cli.go's getMostRecentServiceProviders like every other provider; only its RouteMatcher (which always returns false) is inert.
func (*Provider) Init ¶
func (p *Provider) Init(ctx *service.AppContext) (service.Registerable, error)
Init initializes the AzureServiceBus service backend and handler. The configured port (Settings.Port, default DefaultPort) is only recorded here; the actual TCP bind happens synchronously in Handler.StartWorker, so a port-in-use failure is returned to the caller directly instead of being discovered later from a background goroutine.
type QueueInfo ¶
type QueueInfo struct {
CreatedAt time.Time
Name string
LockDuration time.Duration
DefaultMessageTTL time.Duration
MaxDeliveryCount int
RequiresSession bool
ActiveMessageCount int
DeadLetterMessageCount int
}
QueueInfo is the read-only metadata snapshot returned by GetQueueInfo/ ListQueues.
type SessionBackend ¶
type SessionBackend interface {
RequiresSession(ref EntityRef) (bool, error)
AcceptSessionWait(
ctx context.Context, ref EntityRef, sessionID, owner string, timeout time.Duration,
) (SessionGrant, error)
PeekLockSessionWait(
ctx context.Context, ref EntityRef, sessionID, owner string, timeout time.Duration,
) (MessageInfo, error)
CheckSessionLock(ref EntityRef, sessionID, owner string) error
RenewSessionLock(ref EntityRef, sessionID, owner string) (time.Time, error)
GetSessionState(ref EntityRef, sessionID, owner string) ([]byte, error)
SetSessionState(ref EntityRef, sessionID, owner string, state []byte) error
ReleaseSession(ref EntityRef, sessionID, owner string)
SessionOf(ref EntityRef, owner string) (string, bool)
PeekSession(ref EntityRef, sessionID string, fromSeq int64, maxCount int) ([]MessageInfo, error)
}
SessionBackend is the session surface the AMQP plane needs.
type SessionGrant ¶
SessionGrant describes an acquired session lock.
type Settings ¶
type Settings struct {
// Port is the fixed TCP port for the dedicated Service Bus listener. See
// handler.go's StartWorker for what happens when it's unavailable (fails
// fast; no fallback pool, matching services/azureblob/azurequeue/azuretable).
Port int `` //nolint:lll // config struct tags are intentionally verbose
/* 189-byte string literal not displayed */
// ValidateSAS opts the handler into cryptographic verification of SAS
// (SharedAccessSignature) tokens against DevKeyValue (or a caller-supplied
// key via WithSASValidation), mirroring services/s3's
// --validate-sigv4/PresignSecret opt-in pattern and Blob/Queue/Table's
// WithSharedKeyValidation. Off by default: SAS tokens are always
// structurally parsed (key name + resource scope extracted) but not
// cryptographically checked unless this is set.
// AMQPPort is the plain-text AMQP 1.0 listener port (0 disables). AMQP is
// what every Service Bus SDK speaks; azservicebus reaches it with
// UseDevelopmentEmulator=true in the connection string.
AMQPPort int `` //nolint:lll // config struct tags are intentionally verbose
/* 156-byte string literal not displayed */
// AMQPTLSPort is the AMQP 1.0 over TLS listener port (0 disables). It serves
// a generated self-signed dev certificate for localhost.
AMQPTLSPort int `` //nolint:lll // config struct tags are intentionally verbose
/* 174-byte string literal not displayed */
ValidateSAS bool `` //nolint:lll // config struct tags are intentionally verbose
/* 249-byte string literal not displayed */
}
Settings holds service-level configuration for the Azure Service Bus backend. Fields are picked up by the Kong CLI parser when this struct is embedded in the root CLI command (see cli.go's CLI.AzureServiceBus field), mirroring services/azurequeue's and services/azuretable's Settings pattern.
func DefaultSettings ¶
func DefaultSettings() Settings
DefaultSettings returns the default Settings. Used when no ConfigProvider is available at init time (e.g. tests constructing a Provider directly).
type StorageBackend ¶
type StorageBackend interface {
// CreateQueue creates a queue. cfg is variadic purely so every
// pre-existing call site that only ever passed a name keeps compiling;
// at most cfg[0] is used (see EntityConfig, firstConfig).
CreateQueue(name string, cfg ...EntityConfig) (created bool, err error)
DeleteQueue(name string) error
QueueExists(name string) bool
GetQueueInfo(name string) (QueueInfo, error)
ListQueues() []QueueInfo
// CreateTopic creates a topic. Only cfg's DefaultMessageTTL field is
// meaningful (see EntityConfig's doc comment); see CreateQueue's doc
// comment for why cfg is variadic.
CreateTopic(name string, cfg ...EntityConfig) (created bool, err error)
DeleteTopic(name string) error
TopicExists(name string) bool
GetTopicInfo(name string) (TopicInfo, error)
ListTopics() []TopicInfo
// CreateSubscription creates a subscription of topic. The filter rule
// (if any) is accepted structurally but not stored/evaluated -- every
// rule is treated as match-all (see PARITY.md's filter-evaluation gap).
// Only cfg's LockDuration/MaxDeliveryCount fields are meaningful; see
// CreateQueue's doc comment for why cfg is variadic.
CreateSubscription(topic, name string, cfg ...EntityConfig) (created bool, err error)
DeleteSubscription(topic, name string) error
SubscriptionExists(topic, name string) bool
GetSubscriptionInfo(topic, name string) (SubscriptionInfo, error)
ListSubscriptions(topic string) ([]SubscriptionInfo, error)
// Send enqueues msg on ref. For a queue ref it is appended to that
// queue's own message list. For a topic ref (Topic set, Subscription
// empty) it is fanned out: an independent copy is appended to every one
// of that topic's subscriptions' own message lists, matching real
// Service Bus's one-to-many topic delivery (see AZURE.md section 9's M5
// entry and services/sns's topic/subscription fan-out as the structural
// reference). Returns ErrTopicNotFound if the topic has no
// subscriptions registered as an error -- fan-out to zero subscriptions
// is not an error, matching real Service Bus (the message is simply
// dropped, as no subscription exists to receive it). msg.TimeToLive, if
// set, is capped at the target entity's configured
// DefaultMessageTimeToLive (real Service Bus semantics); if unset, the
// entity's DefaultMessageTimeToLive is used outright. Send also wakes
// any goroutine blocked in PeekLockWait on the affected entity/entities.
Send(ref EntityRef, msg NewMessage) (MessageInfo, error)
// PeekLock performs a destructive read: it returns the oldest visible,
// non-expired message on ref (or its dead-letter sub-queue, if
// deadLetter is true), locking it for lockDuration and incrementing its
// delivery count. lockDuration <= 0 resolves to ref's own configured
// LockDuration (falling back to DefaultLockDuration if ref has none
// configured) rather than a caller-supplied value -- see EntityConfig.
// Returns ErrMessageNotFound if none is available.
PeekLock(ref EntityRef, deadLetter bool, lockDuration time.Duration) (MessageInfo, error)
// PeekLockWait is PeekLock's long-poll variant: if no message is
// immediately visible, it waits up to timeout for one to arrive (a
// message becoming visible via Send, Abandon's release path, or the
// Janitor's lock-release path all wake a waiter), or until ctx is
// cancelled, whichever comes first. timeout <= 0 behaves exactly like an
// immediate PeekLock call. Callers are expected to have already clamped
// timeout to a sane maximum (see handler.go's MaxPeekLockWaitTimeout);
// PeekLockWait itself does not enforce a cap.
PeekLockWait(
ctx context.Context,
ref EntityRef,
deadLetter bool,
lockDuration, timeout time.Duration,
) (MessageInfo, error)
// Complete permanently removes a locked message identified by messageID,
// after verifying lockToken matches. Returns ErrLockTokenMismatch or
// ErrMessageNotFound as appropriate.
Complete(ref EntityRef, deadLetter bool, messageID, lockToken string) error
// Abandon releases a locked message's lock (making it immediately
// available again) after verifying lockToken matches, without altering
// its position. If the message's delivery count has reached ref's
// configured MaxDeliveryCount (see EntityConfig) it is moved to the
// dead-letter sub-queue instead.
Abandon(ref EntityRef, deadLetter bool, messageID, lockToken string) error
// Peek returns up to maxCount non-expired messages with SequenceNumber >=
// fromSeq, oldest first, without locking them or touching DeliveryCount.
Peek(ref EntityRef, deadLetter bool, fromSeq int64, maxCount int) ([]MessageInfo, error)
// ReceiveAndDelete removes and returns the oldest visible message
// (ErrMessageNotFound if none).
ReceiveAndDelete(ref EntityRef, deadLetter bool) (MessageInfo, error)
// DeadLetter moves a locked live message to the dead-letter sub-queue,
// recording reason and description.
DeadLetter(ref EntityRef, messageID, lockToken, reason, description string) error
// Reset clears all in-memory state. Used by the
// POST /_gopherstack/reset endpoint for CI pipelines and rapid local development.
Reset()
}
StorageBackend defines the interface for an Azure Service Bus backend. Shaped after services/azurequeue's StorageBackend: a narrow, testable seam between the wire handler and storage, so handler tests can substitute a fake.
type SubscriptionInfo ¶
type SubscriptionInfo struct {
CreatedAt time.Time
Name string
LockDuration time.Duration
MaxDeliveryCount int
RequiresSession bool
ActiveMessageCount int
DeadLetterMessageCount int
}
SubscriptionInfo is the read-only metadata snapshot returned by GetSubscriptionInfo/ListSubscriptions.
Source Files
¶
- amqp.go
- amqp_addr.go
- amqp_backend.go
- amqp_conn.go
- amqp_mgmt.go
- amqp_msg.go
- amqp_rpc.go
- amqp_session.go
- atom.go
- errors.go
- handler.go
- interfaces.go
- iso8601.go
- janitor.go
- message_ops.go
- mq_index.go
- persistence.go
- provider.go
- queue_ops.go
- receive_handlers.go
- receive_ops.go
- sas.go
- session_ops.go
- settings.go
- store.go
- subscription_ops.go
- topic_ops.go