Documentation
¶
Overview ¶
Package mqttv5 is an MQTT v5 client for Go that allocates little per message, with an ergonomic API. The supervisor (reconnect, replay-in-flight, auto-resubscribe) is baked into every Client; there is no separate "auto-reconnect" wrapper.
Why this package ¶
One package, one Client. No paho / autopaho split — reconnect, replay, and resubscribe are always on. Channel-native subscribe: <-chan *Message (Client.Subscribe), Queue of *Message (Client.SubscribeQueue), or a sync callback (Client.SubscribeCallback). Backpressure is per subscription: a consumer that falls behind loses QoS 0 messages, acknowledged so the broker stops retransmitting, while the broker's flow control holds QoS 1 and 2 messages back unless the subscription opts out with SubAutoAck or a DropOldest queue (see Client.Subscribe).
Two multi-broker patterns are kept distinct:
- Failover within one logical client (interchangeable brokers, one session identity): WithBrokers.
- N parallel sessions to N independent brokers, each treated as itself: ClientGroup. Configurable per-broker auth / TLS / ClientID via GroupMember.Opts; pluggable publish policy (GroupPublishBroadcast, GroupPublishRoundRobin, GroupPublishHashByTopic). A member with several GroupMember.Brokers fails over among them.
Separately, WithPublisherPool adds publish-only connections to the client's own brokers, each with its own window of unacknowledged QoS 1/2 messages and its own read loop, while subscriptions and any persistent session stay on the main connection.
Typed publish / subscribe takes one Encoding per client (WithEncoding) and the payload type per call: Client.SubscribeAs delivers decoded values and Client.PublishValue encodes any value and declares the Encoding's Content Type. JSON and msgpack Encodings ship in separate submodules so the core stays stdlib-only. The durable outbound QueuePublisher lets you enqueue publishes while disconnected, keeps a window of them in flight once connected, and, with the queue/file and store/file submodules, continues the exchanges in flight after a process crash instead of publishing those messages again (a lost broker session aside).
With WithStore, every packet that depends on a session record is sent only once the record is written, and a failed write stops the client as a crash would: the next Client.Connect reloads the session from the store (see WithOnStoreFailure).
Operator surface:
- Client.Stats returns a snapshot of in-memory counters (connects, publishes sent/acked, inbound dropped, pool fallbacks, ping timeouts, ...). Opt in via WithStats; when off, each counter update is a nil check.
- Client.DisconnectWith sends a custom DISCONNECT (reason code, ReasonString, SessionExpiry override).
- WithConnectPacketBuilder mutates the CONNECT immediately before each attempt — the canonical OAuth-token-rotation hook.
- WithOnConnectError / WithOnReconnectAttempt feed metrics and alerting per attempt; WithOnConnectionDown returns false to terminate the supervisor.
The MQTT v5 client feature set: shared subscriptions, Subscription Identifiers, topic aliases (in and out), session expiry and resumption, retained messages, the Will and its properties, server redirects, enhanced authentication (CONNECT and mid-session, §4.12), and the broker's CONNACK limits enforced before a packet is sent. Every packet from the broker is validated before it is acted on.
Lifecycle callbacks ¶
The connection callbacks (WithOnConnectionUp, WithOnConnectionDown, WithOnConnectError, WithOnReconnectAttempt, WithOnServerDisconnect, WithOnServerRedirect, WithOnResubscribeError, WithOnReauthenticated and WithOnStoreFailure) run one at a time, in the order of the events they report, on a goroutine of the client's own. They may call Client.Disconnect, Client.Connect and Client.SetBrokers. Before each reconnect attempt, and before Connect tries the next broker, the client waits for the callbacks posted so far, so a SetBrokers call from one applies to that attempt, and a slow callback delays it. Because they run apart from the calls that cause them, OnConnectionUp may run after Connect returns, and a callback for an event before Disconnect may run after Disconnect returns.
Message handlers are different: Client.SubscribeCallback handlers and SubOnDrop hooks run on the connection's read loop, which Disconnect waits for, so they must not call Disconnect themselves; start it on another goroutine. A teardown held up by one logs a warning after five seconds.
Quick start ¶
cli, err := mqttv5.New(
mqttv5.WithBroker("mqtt://localhost:1883"),
mqttv5.WithClientID("ingest-svc-1"),
)
if err != nil { panic(err) }
ctx := context.Background()
if err := cli.Connect(ctx); err != nil { panic(err) }
defer cli.Disconnect(ctx)
// Channel subscribe — manual ack, §4.6-ordered PUBACK flush.
msgs, _, err := cli.Subscribe(ctx,
[]mqttv5.TopicFilter{{Topic: "events/#", QoS: 1}})
if err != nil { panic(err) }
go func() {
for m := range msgs {
fmt.Printf("%s: %s\n", m.Topic, m.Payload)
_ = m.Ack()
}
}()
// QoS 1 publish — supervisor replays with DUP=1 across drops.
_ = cli.Publish(ctx, PublishOptions{
Topic: "events/example",
Payload: []byte("hello"),
QoS: 1,
})
See the Example functions below for end-to-end snippets covering each subscribe shape, typed payloads, multi-broker fan-out, and the durable queue publisher.
Options naming ¶
Option helpers split by scope: Option (passed to New) configures the Client and is named With…; SubscribeOption (passed inline to Subscribe / SubscribeQueue) configures one subscription and is named Sub…; ClientGroupOption (passed to NewClientGroup) configures the group and is named WithGroup….
Submodules ¶
Each opt-in submodule has its own go.mod so importing it does not add a runtime dependency to the core:
- codec/json — JSON [Codec] implementation, wired into [Typed].
- codec/msgpack — MessagePack [Codec] via vmihailenco/msgpack/v5.
- queue/file — Crash-safe outbound queue for QueuePublisher.
- store/file — Crash-safe session store for in-flight QoS 1/2.
- transport/ws — WebSocket transport via gobwas/ws; wire it in with WithDialFunc.
Architecture ¶
One goroutine per connection drives the read path (read -> decode -> trie match -> handler) with handlers running synchronously on the reader. On a TCP or Unix connection, a publish that waits for its write writes on its own goroutine when the connection is idle; if its ctx ends mid-write, a deadline stops the write and the writer goroutine finishes the packet. When publishers overlap, and on TLS and WebSocket connections, a second goroutine drains a many-producer-single-consumer write channel and coalesces the queued packets, so they do not take turns on a write lock around net.Conn.Write. On unix systems the reader writes the acknowledgements it owes itself when the connection is idle, as far as the socket takes them without waiting, and leaves the rest to the writer goroutine. WithPublisherPool runs N such connections for publishing, each with its own writer and its own Receive Maximum window.
Packets and frame buffers up to 64 KiB come from sync.Pools. An inbound Message owns a copy of its topic, payload and properties, so a pooled frame goes back to its pool before any handler runs; a larger frame is never pooled and becomes the message's own. SubZeroCopy subscriptions alias the frame until every handle is acked.
A supervisor goroutine reconnects with configurable backoff, resumes the session, replays unacknowledged QoS 1/2 publishes with DUP=1 in their original order, and re-issues every tracked subscription.
Benchmarks ¶
See benchmarks/ for end-to-end and codec benchmarks against eclipse/paho.golang and eclipse/paho.mqtt.golang.
Example ¶
Example shows the minimal connect → subscribe → publish loop. The supervisor (reconnect, replay, resubscribe) is implicit — once Connect returns, every drop is recovered automatically until Disconnect.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, err := mqttv5.New(
mqttv5.WithBroker("mqtt://localhost:1883"),
mqttv5.WithClientID("example"),
)
if err != nil {
panic(err)
}
ctx := context.Background()
if err := cli.Connect(ctx); err != nil {
panic(err)
}
defer cli.Disconnect(ctx)
msgs, _, err := cli.Subscribe(ctx, []mqttv5.TopicFilter{{Topic: "demo/#", QoS: 1}})
if err != nil {
panic(err)
}
go func() {
for m := range msgs {
fmt.Printf("recv %s %s\n", m.Topic, m.Payload)
_ = m.Ack()
}
}()
_ = cli.Publish(ctx, mqttv5.PublishOptions{
Topic: "demo/hello",
Payload: []byte("world"),
QoS: 1,
})
}
Output:
Index ¶
- Constants
- Variables
- func EncodePublish(o PublishOptions, id uint16) ([]byte, error)
- func PermanentPublishError(err error) bool
- type Authenticator
- type Backoff
- type Client
- func (c *Client) AwaitConnection(ctx context.Context) error
- func (c *Client) ClientID() string
- func (c *Client) Connect(ctx context.Context) error
- func (c *Client) Connected() bool
- func (c *Client) Disconnect(ctx context.Context) error
- func (c *Client) DisconnectWith(ctx context.Context, opts DisconnectOptions) error
- func (c *Client) Publish(ctx context.Context, po PublishOptions) error
- func (c *Client) PublishValue(ctx context.Context, opts PublishOptions, v any) error
- func (c *Client) Reauthenticate(ctx context.Context) error
- func (c *Client) ServerInfo() (ConnackInfo, bool)
- func (c *Client) SetBrokers(urls ...string) error
- func (c *Client) Stats() Stats
- func (c *Client) Subscribe(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *Message, SubscriptionToken, error)
- func (c *Client) SubscribeAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *TypedMessage[T], SubscriptionToken, error)
- func (c *Client) SubscribeCallback(ctx context.Context, filters []TopicFilter, h HandlerFunc, ...) (SubscriptionToken, error)
- func (c *Client) SubscribeCallbackAs[T any](ctx context.Context, filters []TopicFilter, h func(*TypedMessage[T]), ...) (SubscriptionToken, error)
- func (c *Client) SubscribeQueue(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*Message], SubscriptionToken, error)
- func (c *Client) SubscribeQueueAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*TypedMessage[T]], SubscriptionToken, error)
- func (c *Client) Unsubscribe(ctx context.Context, token SubscriptionToken) error
- type ClientGroup
- func (g *ClientGroup) Connect(ctx context.Context) error
- func (g *ClientGroup) Disconnect(ctx context.Context) error
- func (g *ClientGroup) Member(name string) *Client
- func (g *ClientGroup) Members() []*Client
- func (g *ClientGroup) Names() []string
- func (g *ClientGroup) Publish(ctx context.Context, opts PublishOptions) ([]GroupResult, error)
- func (g *ClientGroup) PublishValue(ctx context.Context, opts PublishOptions, v any) ([]GroupResult, error)
- func (g *ClientGroup) Subscribe(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *Message, map[string]SubscriptionToken, error)
- func (g *ClientGroup) SubscribeAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *TypedMessage[T], map[string]SubscriptionToken, error)
- func (g *ClientGroup) SubscribeQueue(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*Message], map[string]SubscriptionToken, error)
- func (g *ClientGroup) SubscribeQueueAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*TypedMessage[T]], map[string]SubscriptionToken, error)
- func (g *ClientGroup) Unsubscribe(ctx context.Context, name string, token SubscriptionToken) error
- func (g *ClientGroup) UnsubscribeAll(ctx context.Context, tokens map[string]SubscriptionToken) error
- type ClientGroupOption
- type Config
- type ConnackInfo
- type ConnectOptions
- type ContextAuthenticator
- type DisconnectInfo
- type DisconnectOptions
- type DropPolicy
- type Encoding
- type GroupError
- type GroupMember
- type GroupPublishPolicy
- type GroupResult
- type GroupSuccess
- type HandlerFunc
- type MemoryPublisherQueue
- func (q *MemoryPublisherQueue) Ack(_ context.Context, seq uint64) error
- func (q *MemoryPublisherQueue) Close() error
- func (q *MemoryPublisherQueue) Enqueue(_ context.Context, e QueueEntry, limit QueueLimit) (uint64, []QueueEntry, error)
- func (q *MemoryPublisherQueue) Len(context.Context) (int, error)
- func (q *MemoryPublisherQueue) Peek(_ context.Context, after uint64, n int) ([]QueueEntry, error)
- type Message
- type Option
- func WithAuthenticator(a Authenticator) Option
- func WithBroker(url string) Option
- func WithBrokers(urls ...string) Option
- func WithCleanStart(b bool) Option
- func WithCleanStartOnReconnect(b bool) Option
- func WithClientID(id string) Option
- func WithConnectPacketBuilder(fn func(ctx context.Context, opts *ConnectOptions) error) Option
- func WithConnectTimeout(d time.Duration) Option
- func WithConnectUserProperties(p []wire.UserProperty) Option
- func WithConnectUserProperty(key, value string) Option
- func WithCredentials(user string, pass []byte) Option
- func WithDialFunc(fn transport.DialFunc) Option
- func WithDialer(d *net.Dialer) Option
- func WithDisconnectFlushTimeout(d time.Duration) Option
- func WithDropPolicy(p DropPolicy) Option
- func WithEncoding(e Encoding) Option
- func WithFollowServerRedirects() Option
- func WithInboundTopicAliasMaximum(n uint16) Option
- func WithKeepAlive(s uint16) Option
- func WithLenientDecoding() Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxSubscribeQueueSize(n int) Option
- func WithMaximumPacketSize(n uint32) Option
- func WithOnConnectError(fn func(error)) Option
- func WithOnConnectionDown(fn func() bool) Option
- func WithOnConnectionUp(fn func(ConnackInfo)) Option
- func WithOnReauthenticated(fn func()) Option
- func WithOnReconnectAttempt(fn func(attempt int, brokerURL string)) Option
- func WithOnResubscribeError(fn func(SubscriptionToken, error)) Option
- func WithOnServerDisconnect(fn func(DisconnectInfo)) Option
- func WithOnServerRedirect(fn func(ServerRedirect)) Option
- func WithOnStoreFailure(fn func(err error)) Option
- func WithOutboundTopicAliases() Option
- func WithPingTimeout(d time.Duration) Option
- func WithPublishMode(m PublishMode) Option
- func WithPublisherPool(size int) Option
- func WithPublisherPoolClientIDFn(fn func(parent string, idx int) string) Option
- func WithPublisherPoolRouting(p PoolRoutingPolicy) Option
- func WithQoSDowngrade() Option
- func WithReadBufferSize(n int) Option
- func WithReceiveMaximum(n uint16) Option
- func WithReconnectBackoff(b Backoff) Option
- func WithRequestProblemInformation(b bool) Option
- func WithRequestResponseInformation(b bool) Option
- func WithRetryInitialConnect() Option
- func WithSessionExpiry(s uint32) Option
- func WithSessionLossPolicy(p SessionLossPolicy) Option
- func WithStats() Option
- func WithStore(s session.Store) Option
- func WithTLSConfig(t *tls.Config) Option
- func WithWill(w *WillOptions) Option
- func WithWriteBatch(n int) Option
- func WithWriteOverflowPolicy(p WriteOverflowPolicy) Option
- func WithWriteQueueSize(n int) Option
- func WithoutKeepAlive() Option
- type PacketType
- type PoolRoutingPolicy
- type Properties
- func (p Properties) ContentType() string
- func (p Properties) CorrelationData() []byte
- func (p Properties) MessageExpiryInterval() (uint32, bool)
- func (p Properties) PayloadFormatIndicator() (byte, bool)
- func (p Properties) ResponseTopic() string
- func (p Properties) SubscriptionIdentifiers() iter.Seq[uint32]
- func (p Properties) UserProperties() iter.Seq2[string, string]
- func (p Properties) UserProperty(key string) (string, bool)
- type ProtocolError
- type PublishMode
- type PublishOptions
- type PublisherQueue
- type Queue
- type QueueEntry
- type QueueLimit
- type QueueOption
- func WithDeadLetter(fn func(QueueEntry, error)) QueueOption
- func WithQueueClassifier(fn func(err error) bool) QueueOption
- func WithQueueDropPolicy(p DropPolicy) QueueOption
- func WithQueueIdempotencyKey() QueueOption
- func WithQueueMaxSize(n int) QueueOption
- func WithQueueRetryBackoff(b Backoff) QueueOption
- func WithQueueTTL(d time.Duration) QueueOption
- func WithQueueWindow(n int) QueueOption
- type QueuePublisher
- type ReasonCode
- type ReasonCodeError
- type RejectedByDisconnectError
- type ServerFinalVerifier
- type ServerRedirect
- type SessionLossPolicy
- type Stats
- type StoreError
- type SubscribeError
- type SubscribeOption
- func SubAutoAck() SubscribeOption
- func SubBuffer(n int) SubscribeOption
- func SubDropPolicy(p DropPolicy) SubscribeOption
- func SubEncoding(e Encoding) SubscribeOption
- func SubMaxQueueSize(n int) SubscribeOption
- func SubOnDecodeError(fn func(*Message, error)) SubscribeOption
- func SubOnDrop(fn func(*Message)) SubscribeOption
- func SubZeroCopy() SubscribeOption
- type SubscribeResult
- type SubscriptionToken
- type TopicFilter
- type TypedMessage
- type UnsubscribeError
- type UnsubscribeResult
- type UserProperty
- type WillOptions
- type WriteOverflowPolicy
Examples ¶
Constants ¶
const ( // DefaultKeepAlive is the keepalive interval used when // WithKeepAlive is not called. MQTT v5 spec range is 1..65535 // seconds. DefaultKeepAlive uint16 = 30 // DefaultConnectTimeout bounds the CONNECT handshake (dial + // CONNECT/CONNACK) when WithConnectTimeout is not called. DefaultConnectTimeout = 10 * time.Second // DefaultPingTimeout caps how long the client waits for any packet // after a PINGREQ before declaring the connection dead. The default // is the smaller of this and half the keep-alive, so a dead // connection is always detected before the next PINGREQ would be // due. With DefaultKeepAlive (30 s) the dead-detect time is 40 s, // inside a broker's 1.5×KeepAlive (45 s) disconnect window. DefaultPingTimeout = 10 * time.Second // DefaultSessionExpiry is the Session Expiry Interval (§3.1.2.11.2) // advertised on every CONNECT. 5 minutes is long enough to ride out // the typical reconnect blip / container redeploy window so QoS 1/2 // resume actually works out of the box; short enough that the // broker isn't asked to hold idle sessions indefinitely. DefaultSessionExpiry uint32 = 300 // DefaultWriteQueueSize sizes the internal MPSC write channel. DefaultWriteQueueSize = 256 // DefaultDisconnectFlushTimeout caps how long writeDisconnectAndClose // will wait for the DISCONNECT packet to flush before the // connection is forcibly torn down. DefaultDisconnectFlushTimeout = 500 * time.Millisecond // DefaultReadBufferSize is the per-connection read window used when // [WithReadBufferSize] is not called. 16 KiB cuts read syscalls for // streams of small messages fourfold against 4 KiB; larger windows // gain little (BenchmarkReceiveWindow). DefaultReadBufferSize = 16 << 10 // DefaultMaxSubscribeQueueSize caps each [Client.SubscribeQueue] // when no cap is configured. At roughly 200 B of overhead plus the // payload per queued message, a full default queue of small // messages holds about 13 MiB plus payloads. DefaultMaxSubscribeQueueSize = 65536 // DefaultReceiveMaximum is the Receive Maximum the client advertises // when [WithReceiveMaximum] is not called. DefaultReceiveMaximum = 256 // UnboundedQueue, passed to [WithMaxSubscribeQueueSize] or // [SubMaxQueueSize], removes the queue cap. A consumer that falls // behind then grows memory without limit. UnboundedQueue = -1 )
Default values used by Config.defaults. Exported so callers can reference them when overriding selectively.
const ( ReasonSuccess = wire.ReasonSuccess ReasonNormalDisconnection = wire.ReasonNormalDisconnection ReasonGrantedQoS0 = wire.ReasonGrantedQoS0 ReasonGrantedQoS1 = wire.ReasonGrantedQoS1 ReasonGrantedQoS2 = wire.ReasonGrantedQoS2 ReasonDisconnectWithWill = wire.ReasonDisconnectWithWill ReasonNoMatchingSubscribers = wire.ReasonNoMatchingSubscribers ReasonNoSubscriptionExisted = wire.ReasonNoSubscriptionExisted ReasonContinueAuthentication = wire.ReasonContinueAuthentication ReasonReAuthenticate = wire.ReasonReAuthenticate ReasonUnspecifiedError = wire.ReasonUnspecifiedError ReasonMalformedPacket = wire.ReasonMalformedPacket ReasonProtocolError = wire.ReasonProtocolError ReasonImplementationSpecificError = wire.ReasonImplementationSpecificError ReasonUnsupportedProtocolVersion = wire.ReasonUnsupportedProtocolVersion ReasonClientIdentifierNotValid = wire.ReasonClientIdentifierNotValid ReasonBadUsernameOrPassword = wire.ReasonBadUsernameOrPassword ReasonNotAuthorized = wire.ReasonNotAuthorized ReasonServerBusy = wire.ReasonServerBusy ReasonBanned = wire.ReasonBanned ReasonServerShuttingDown = wire.ReasonServerShuttingDown ReasonBadAuthenticationMethod = wire.ReasonBadAuthenticationMethod ReasonKeepAliveTimeout = wire.ReasonKeepAliveTimeout ReasonSessionTakenOver = wire.ReasonSessionTakenOver ReasonTopicFilterInvalid = wire.ReasonTopicFilterInvalid ReasonTopicNameInvalid = wire.ReasonTopicNameInvalid ReasonPacketIdentifierInUse = wire.ReasonPacketIdentifierInUse ReasonPacketIdentifierNotFound = wire.ReasonPacketIdentifierNotFound ReasonReceiveMaximumExceeded = wire.ReasonReceiveMaximumExceeded ReasonTopicAliasInvalid = wire.ReasonTopicAliasInvalid ReasonPacketTooLarge = wire.ReasonPacketTooLarge ReasonMessageRateTooHigh = wire.ReasonMessageRateTooHigh ReasonQuotaExceeded = wire.ReasonQuotaExceeded ReasonAdministrativeAction = wire.ReasonAdministrativeAction ReasonPayloadFormatInvalid = wire.ReasonPayloadFormatInvalid ReasonRetainNotSupported = wire.ReasonRetainNotSupported ReasonQoSNotSupported = wire.ReasonQoSNotSupported ReasonUseAnotherServer = wire.ReasonUseAnotherServer ReasonServerMoved = wire.ReasonServerMoved ReasonConnectionRateExceeded = wire.ReasonConnectionRateExceeded ReasonMaximumConnectTime = wire.ReasonMaximumConnectTime ReasonSubscriptionIDsNotSupported = wire.ReasonSubscriptionIDsNotSupported ReasonWildcardSubscriptionsNotSupported = wire.ReasonWildcardSubscriptionsNotSupported )
Reason codes, as named by MQTT v5 §2.4. Several share a value; the packet they arrive in decides the meaning.
const ( CONNECT = wire.CONNECT CONNACK = wire.CONNACK PUBLISH = wire.PUBLISH PUBACK = wire.PUBACK PUBREC = wire.PUBREC PUBREL = wire.PUBREL PUBCOMP = wire.PUBCOMP SUBSCRIBE = wire.SUBSCRIBE SUBACK = wire.SUBACK UNSUBSCRIBE = wire.UNSUBSCRIBE UNSUBACK = wire.UNSUBACK PINGREQ = wire.PINGREQ PINGRESP = wire.PINGRESP DISCONNECT = wire.DISCONNECT AUTH = wire.AUTH )
Control packet types.
const DefaultPublisherPoolClientIDFormat = "%s-pub-%d"
DefaultPublisherPoolClientIDFormat is the format string used by the default pool-member ClientID function: parent ClientID, then "-pub-", then the 1-based member index.
const ( // DefaultQueueWindow is how many queued messages a QueuePublisher // publishes at once when [WithQueueWindow] is not set. DefaultQueueWindow = 32 )
const DefaultSubscribeBuffer = 64
DefaultSubscribeBuffer is the channel buffer size used by Subscribe when SubBuffer is not called: how many QoS 0 messages the channel holds before it drops one.
const QueueIDProperty = "mqttv5-msg-id"
QueueIDProperty is the user property WithQueueIdempotencyKey adds to each queued message, holding its QueueEntry.ID.
Variables ¶
var ( // ErrQueueClosed is returned by a [PublisherQueue] or // [QueuePublisher] after Close. ErrQueueClosed = errors.New("mqttv5: queue is closed") // ErrQueueFull is returned when a bounded queue has no room: under // [DropNewest] when it holds its maximum, under [DropOldest] when // every entry it holds is already being published. ErrQueueFull = errors.New("mqttv5: queue is full") )
var ( ErrNotConnected = errors.New("mqttv5: client not connected") ErrAlreadyConnected = errors.New("mqttv5: client already connected") ErrClosed = errors.New("mqttv5: client closed") ErrConnectRefused = errors.New("mqttv5: broker refused CONNECT") ErrUnexpectedPacket = errors.New("mqttv5: unexpected packet from broker") // Enhanced-authentication errors (§4.12), returned by Reauthenticate. // ErrReauthRejected is wrapped with the broker's DISCONNECT reason // code (mirroring ErrConnectRefused) — match with errors.Is. ErrNoAuthenticator = errors.New("mqttv5: no Authenticator configured") ErrReauthInProgress = errors.New("mqttv5: re-authentication already in progress") ErrReauthRejected = errors.New("mqttv5: broker rejected re-authentication") // ErrWriteQueueFull is returned by QoS 0 Publish when the client // is configured with WriteDropNewest and the writer queue has no // room. The publish never reaches the wire. ErrWriteQueueFull = errors.New("mqttv5: write queue full") // Subscribe-time errors raised when the broker advertised the // feature as unsupported in CONNACK (MQTT v5 §3.2.2.3.{11,13}). // Returned before any SUBSCRIBE traffic goes on the wire. ErrWildcardSubsUnsupported = errors.New("mqttv5: broker does not support wildcard subscriptions") // ErrSessionLost completes a QoS 1/2 Publish whose message was // discarded because a connection started without the old session // and [WithSessionLossPolicy] is [SessionLossFail]. ErrSessionLost = inflight.ErrSessionLost // ErrMessageExpired completes a QoS 1/2 Publish whose Message Expiry // Interval ran out before it could be sent again after a session // loss. ErrMessageExpired = inflight.ErrMessageExpired // ErrPacketIDsExhausted is returned when all 65,535 packet // identifiers are in use and the caller's context ends first. ErrPacketIDsExhausted = inflight.ErrIDsExhausted // ErrStoreFailed is matched by every [*StoreError]: a write to the // [WithStore] store failed. ErrStoreFailed = inflight.ErrStoreFailed // ErrQoSNotSupported is returned by Publish for a QoS above the // broker's Maximum QoS (see [WithQoSDowngrade]), and completes a // stored message that a reconnect to such a broker can no longer // send. ErrQoSNotSupported = inflight.ErrQoSNotSupported // ErrInvalidSessionExpiry is returned by [Client.DisconnectWith] // for a non-zero Session Expiry Interval when the CONNECT carried 0 // [MQTT-3.14.2-2]. Nothing is sent. ErrInvalidSessionExpiry = errors.New("mqttv5: DISCONNECT cannot set a Session Expiry Interval when CONNECT sent 0") // ErrTopicAliasInvalid is returned by Publish for a Topic Alias the // broker's Topic Alias Maximum does not allow, or for a QoS 1/2 // message that has an alias but no topic name. ErrTopicAliasInvalid = errors.New("mqttv5: invalid topic alias") // ErrInvalidTopic is returned for a topic name or filter MQTT v5 // forbids (§4.7): wildcards in a topic name, misplaced wildcards in // a filter, a malformed shared subscription, NUL or invalid UTF-8. ErrInvalidTopic = wire.ErrInvalidTopic // ErrInvalidField is returned for any other option value MQTT v5 // forbids, such as No Local on a shared subscription or DUP on a // QoS 0 publish. Nothing is sent. ErrInvalidField = wire.ErrInvalidField // ErrFieldTooLong is returned for a string or binary field over the // 65,535 bytes the protocol can encode. ErrFieldTooLong = wire.ErrStringTooLong // ErrRetainNotSupported is returned by Publish for a retained // message when the broker's Retain Available is 0. ErrRetainNotSupported = inflight.ErrRetainNotSupported // ErrPacketTooLarge is returned for a packet larger than the // broker's Maximum Packet Size, and matches a broker refusal with // reason code 0x95. ErrPacketTooLarge = inflight.ErrPacketTooLarge // Sentinels matched by [ReasonCodeError] for the broker refusals // callers most often branch on. ErrNotAuthorized = errors.New("mqttv5: not authorized") ErrQuotaExceeded = errors.New("mqttv5: quota exceeded") ErrTopicNameInvalid = errors.New("mqttv5: topic name invalid") ErrPayloadFormatInvalid = errors.New("mqttv5: payload format invalid") )
Errors returned by the client.
var DefaultQueueRetryBackoff = ExponentialBackoff(time.Second, time.Minute, 500*time.Millisecond)
DefaultQueueRetryBackoff spaces the attempts of a message whose publish failed for a reason that may pass (a refusal such as 0x97 Quota exceeded, a store error).
var DefaultReconnectBackoff = ExponentialBackoff( time.Second, 30*time.Second, 200*time.Millisecond, )
DefaultReconnectBackoff is used when WithReconnectBackoff is not called: 1s -> 2s -> 4s -> ... up to 30s with 200ms jitter.
var ErrChanDropOldestUnsupported = errors.New(
"mqttv5: Subscribe (chan) does not support DropOldest; use SubscribeQueue",
)
ErrChanDropOldestUnsupported is returned by Client.Subscribe when called with SubDropPolicy(DropOldest); use Client.SubscribeQueue for DropOldest semantics.
var ErrInvalidBrokerURL = errors.New("mqttv5: invalid broker URL")
ErrInvalidBrokerURL is returned when a broker URL fails to parse or has an unsupported scheme. WithDialFunc relaxes scheme validation.
var ErrMissingBroker = errors.New("mqttv5: missing broker URL (use WithBroker)")
ErrMissingBroker is returned by New when no broker URL was provided.
var ErrNilHandler = errors.New("mqttv5: subscribe handler must not be nil")
ErrNilHandler is returned by Client.SubscribeCallback when the handler argument is nil.
var ErrNoEncoding = errors.New("mqttv5: no Encoding: set WithEncoding, or SubEncoding for one subscription")
ErrNoEncoding is returned by a typed call on a client without an Encoding: set one with WithEncoding, or with SubEncoding for one subscription.
var ErrQoS0NotQueueable = errors.New("mqttv5: QueuePublisher requires QoS >= 1")
ErrQoS0NotQueueable is returned by QueuePublisher.Publish for a QoS 0 message: with no acknowledgement there is nothing to keep it queued for.
Functions ¶
func EncodePublish ¶ added in v0.11.0
func EncodePublish(o PublishOptions, id uint16) ([]byte, error)
EncodePublish encodes o as a PUBLISH packet with packet identifier id (ignored for QoS 0). For tools and storage formats that need the message's exact MQTT encoding, such as a PublisherQueue.
func PermanentPublishError ¶ added in v0.11.0
PermanentPublishError reports whether err means the broker will never accept the message as it is: PUBACK/PUBREC 0x87 Not authorized, 0x90 Topic Name invalid, 0x95 Packet too large, 0x99 Payload format invalid, 0x9A Retain not supported, 0x9B QoS not supported, or the equivalent checks against the broker's CONNACK limits and MQTT's rules before sending. 0x97 Quota exceeded and other failures may pass.
Types ¶
type Authenticator ¶
type Authenticator interface {
Method() string
Begin(ctx context.Context) (data []byte, err error)
Continue(brokerData []byte) (response []byte, done bool, err error)
}
Authenticator drives the client side of MQTT v5 enhanced authentication — the challenge-response handshake that runs during CONNECT and during re-authentication (SCRAM, Kerberos, OAuth/JWT, etc.).
Method() supplies the AuthenticationMethod property. Begin(ctx) resolves the initial AuthenticationData — for a bearer method this is where a fresh token is fetched, so ctx carries the deadline of the triggering operation (the connect timeout, or the ctx passed to Reauthenticate); honor it for any I/O. For each broker AUTH challenge — during CONNECT (before CONNACK) and during mid-session re-authentication (§4.12) — Continue(brokerData) returns the next response, which the client always sends as AUTH 0x18 Continue.
done is informational only. The client never concludes the exchange: per §3.15.2.1 only the server sends AUTH 0x00 Success (or CONNACK for the initial handshake). Returning done=true does NOT make the client emit 0x00 — treat it as a hint that the Authenticator has no further data to send. A non-nil error from Continue aborts the connection.
Implementations need only be safe for concurrent use when shared across multiple Clients (e.g. with WithPublisherPool).
type Backoff ¶
Backoff returns the delay before retry attempt N. The first retry after a failed connect is attempt 0 (the initial connect itself runs immediately, no backoff). Implementations must be safe for concurrent use.
func ConstantBackoff ¶
ConstantBackoff returns the same delay for every attempt. Predictable but vulnerable to thundering-herd reconnect storms; ExponentialBackoff is usually preferable.
func ExponentialBackoff ¶
ExponentialBackoff doubles min each attempt up to max with ±jitter added (range [computed-jitter, computed+jitter]).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is an MQTT v5 client. After New(opts...), call Connect to establish the first connection and start the supervisor that reconnects on drop.
func New ¶
New constructs a Client. Apply WithBroker (required) plus any other options. The Client is not connected — call Connect.
func (*Client) AwaitConnection ¶ added in v0.11.0
AwaitConnection waits until the client is connected and returns nil, at once if it already is. It returns ErrClosed when the client is disconnected, or ctx's error. Pair with WithRetryInitialConnect to start without the broker and wait for it.
func (*Client) ClientID ¶
ClientID returns the ClientID currently in effect: the one configured with WithClientID, or, when that was empty, the identifier the broker assigned (AssignedClientIdentifier, §3.2.2.3.7). The assigned identifier is reused on every later CONNECT so the session can resume, and is kept in the WithStore store across restarts.
func (*Client) Connect ¶
Connect performs the initial CONNECT/CONNACK handshake (bounded by ctx) and starts the supervisor. With WithBrokers it tries each broker once, from a random one on, and fails only when none accepts, with every broker's error. Reconnect attempts run on internal background contexts until Client.Disconnect. With WithRetryInitialConnect a failed handshake is retried the same way and Connect returns nil; see Client.AwaitConnection. Returns ErrAlreadyConnected when called twice without an intervening Disconnect; while a Disconnect, or a supervisor told to stop reconnecting, is still tearing down, Connect waits for it. A Disconnect during Connect cancels it, and Connect returns ErrClosed.
func (*Client) Disconnect ¶
Disconnect sends a graceful DISCONNECT (reason Normal Disconnection), stops the supervisor, and tears down the current connection. For a custom reason / properties use Client.DisconnectWith. Does not fire OnConnectionDown. Idempotent.
Lifecycle callbacks may call Disconnect. Client.SubscribeCallback handlers and SubOnDrop hooks must not: Disconnect waits for the read loop that runs them, so it would wait for itself. Start it on another goroutine.
func (*Client) DisconnectWith ¶
func (c *Client) DisconnectWith(ctx context.Context, opts DisconnectOptions) error
DisconnectWith sends a DISCONNECT with opts, stops the supervisor, and tears down the current connection. With WithPublisherPool it then refuses new publishes with ErrClosed and waits, bounded by ctx, for those in flight on pool members, whose sessions end with their connections, before disconnecting them. It returns ctx's error if ctx ended before the session store writes or the pool's publishes were done, joined with the pool members' disconnect errors. Idempotent. Like Client.Disconnect, it must not be called from a SubscribeCallback handler or SubOnDrop hook.
func (*Client) Publish ¶
func (c *Client) Publish(ctx context.Context, po PublishOptions) error
Publish sends a PUBLISH packet.
- QoS 0: returns per PublishMode (PublishFireAndForget or PublishWaitForFlush).
- QoS 1: returns after PUBACK.
- QoS 2: returns after PUBCOMP.
QoS 1/2 messages belong to the session once registered: a dropped connection resends them (DUP=1) when the session resumes, and callers stay blocked on the acknowledgement across the drop. A refusal (reason code 0x80 or above) is returned as a *ReasonCodeError.
A call that waits for its packet to be written — QoS 1/2, or QoS 0 with PublishWaitForFlush — writes it on the calling goroutine when the connection is a TCP or Unix socket and nothing is queued for its writer goroutine, saving the hand-off; otherwise the writer sends it behind the queued packets. ctx bounds the call either way — a write still blocked when ctx is cancelled stops within 20 ms, one at ctx's deadline at once — and a write it interrupts is finished by the writer goroutine, so the packet may still reach the broker after Publish returned ctx's error.
With WithPublisherPool the call first tries the pool per PoolRoutingPolicy and falls back to the main connection if every pool member is unhealthy.
Example ¶
ExampleClient_Publish sends a QoS 1 publish. Publish returns after PUBACK arrives. If the connection drops mid-flight the session entry persists and the supervisor replays the packet with DUP=1 on reconnect; the caller stays blocked across the drop.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, _ := mqttv5.New(mqttv5.WithBroker("mqtt://localhost:1883"))
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
err := cli.Publish(ctx, mqttv5.PublishOptions{
Topic: "events/example",
Payload: []byte(`{"value":42}`),
QoS: 1,
})
if err != nil {
fmt.Println("publish:", err)
}
}
Output:
func (*Client) PublishValue ¶ added in v0.12.0
PublishValue encodes v with the client's Encoding and publishes it as Client.Publish does, declaring the Encoding's Content Type unless opts sets one. opts.Payload must be nil: v is the payload.
func (*Client) Reauthenticate ¶ added in v0.10.0
Reauthenticate initiates MQTT v5 re-authentication (§4.12) on each live connection the client holds: the main one and, with WithPublisherPool, every connected pool member, all at once. On each connection it sends an AUTH 0x19 carrying the configured Authenticator's Method() and a fresh Begin(ctx) payload, services any broker 0x18 Continue challenges via Authenticator.Continue, and returns when the broker concludes the exchange — without dropping the connection or disturbing in-flight QoS state.
It returns nil when the broker accepts re-authentication (AUTH 0x00 Success), or:
- ErrNoAuthenticator if the client was not built WithAuthenticator;
- ErrNotConnected if there is no live connection (before the first CONNACK, or after a drop);
- ErrReauthInProgress if another re-auth is already in flight on this connection (calls are single-flighted per connection);
- ErrReauthRejected (wrapped with the DISCONNECT reason code) if the broker rejects re-auth and tears the connection down;
- ctx.Err() if ctx is cancelled or its deadline elapses first.
The AuthenticationMethod established at CONNECT is reused; Method() MUST be stable for the client's lifetime (§4.12 requires it to match across the exchange). Reauthenticate is safe for concurrent use.
ctx bounds the whole operation including credential resolution via Begin(ctx). On ctx cancellation after the AUTH 0x19 has been sent the connection is left intact and the exchange resolves on the wire — re-auth stays single-flighted until the broker concludes, so a subsequent call may observe ErrReauthInProgress until then. On a broker rejection the connection is torn down and the supervisor reconnects through the normal CONNECT path (which re-presents credentials).
With a publisher pool the outcomes above describe the main connection, joined with any pool member's failure (prefixed with the member), so errors.Is sees every one: a rejected member reconnects as the main connection would, and one lost during its exchange reports ErrNotConnected. A member not connected when the call starts is skipped; its next CONNECT presents a fresh credential.
func (*Client) ServerInfo ¶ added in v0.11.0
func (c *Client) ServerInfo() (ConnackInfo, bool)
ServerInfo returns what the broker granted on the current connection, and false when the client is not connected.
func (*Client) SetBrokers ¶
SetBrokers replaces the broker URL list at runtime, for the main connection and every publisher pool member; each one's next reconnect uses it. Typical caller: WithOnServerDisconnect on a ServerMoved / UseAnotherServer redirect. URLs are validated as in New.
Example ¶
ExampleClient_SetBrokers shows multi-broker failover. The supervisor rotates through mqttv5.Config.BrokerURLs on every reconnect attempt; mqttv5.Client.SetBrokers replaces the list at runtime — the canonical hook is a ServerMoved DISCONNECT.
package main
import (
"crypto/tls"
"github.com/ashtonian/mqttv5"
)
func main() {
var cli *mqttv5.Client
cli, _ = mqttv5.New(
mqttv5.WithBrokers(
"mqtts://broker-a.example.com:8883",
"mqtts://broker-b.example.com:8883",
),
mqttv5.WithTLSConfig(&tls.Config{MinVersion: tls.VersionTLS13}),
mqttv5.WithOnServerDisconnect(func(d mqttv5.DisconnectInfo) {
if d.ServerReference != "" {
_ = cli.SetBrokers(d.ServerReference)
}
}),
)
_ = cli
}
Output:
func (*Client) Stats ¶
Stats returns a snapshot of the Client's counters, its publisher pool's included. Cheap to call — the returned value is detached. Returns the zero value when stats are disabled (WithStats not set on the Config).
func (*Client) Subscribe ¶
func (c *Client) Subscribe(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *Message, SubscriptionToken, error)
Subscribe sends one SUBSCRIBE for filters and returns a buffered channel of matching inbound messages. Callers MUST call Message.Ack on each. Returns once the SUBACK arrives — if the client is not connected, or the connection drops first, the SUBSCRIBE goes out on the next connection — or when ctx ends. The channel closes on Client.Unsubscribe or Client.Disconnect, or when the broker refuses every filter.
Called before Client.Connect, or between Disconnect and the next Connect, Subscribe returns at once and the SUBSCRIBE goes out first on the connection Connect makes: messages a resumed session holds, which the broker sends right after its CONNACK, reach the subscription. token.Results() and token.Err() report the SUBACK once it arrives; if that Connect fails, the subscription ends with it.
When the broker refuses filters Subscribe returns a *SubscribeError together with the channel and token: the channel carries the granted filters, or is already closed if none were. token.Results() holds each filter's outcome.
The broker keeps one subscription per filter: subscriptions of this client with the same filter share it, each receives every message for it, and the options of the most recent Subscribe apply to all of them.
A consumer that falls behind loses QoS 0 messages, never QoS 1 or 2: once the channel holds SubBuffer messages, a QoS 0 message is dropped and acked (observe drops via SubOnDrop), while QoS 1 and 2 messages have room for WithReceiveMaximum more. That is as many as the broker sends before the consumer acks one, so the broker's flow control holds the rest back. The exceptions: with SubAutoAck, QoS 1 and 2 messages are dropped as QoS 0 ones are, and messages left unread from a session the broker discarded take room of their own. Returns ErrChanDropOldestUnsupported when called with explicit SubDropPolicy of DropOldest.
Example ¶
ExampleClient_Subscribe receives matched messages on a buffered channel. The caller MUST call mqttv5.Message.Ack on each one — for QoS 1/2 the broker-side ack is held until Ack is called, in §4.6 arrival order across overlapping filters.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, _ := mqttv5.New(mqttv5.WithBroker("mqtt://localhost:1883"))
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
msgs, token, err := cli.Subscribe(ctx,
[]mqttv5.TopicFilter{{Topic: "events/#", QoS: 1}},
mqttv5.SubBuffer(256),
mqttv5.SubOnDrop(func(m *mqttv5.Message) {
// Dropped messages have already been ack'd; this is for
// observability only. Must not call Ack and must not retain
// Topic/Payload past return.
fmt.Println("dropped", m.Topic)
}),
)
if err != nil {
return
}
for m := range msgs {
fmt.Println(m.Topic, string(m.Payload))
_ = m.Ack()
}
_ = cli.Unsubscribe(ctx, token)
}
Output:
func (*Client) SubscribeAs ¶ added in v0.12.0
func (c *Client) SubscribeAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *TypedMessage[T], SubscriptionToken, error)
SubscribeAs is Client.Subscribe with each payload decoded into a T by the subscription's Encoding: SubEncoding, else the client's. Decoding runs on the read goroutine as messages arrive; a payload the Encoding rejects is not delivered (see SubOnDecodeError). Buffering, drops, refusals and closing are those of Client.Subscribe, and each message must be acked.
Example ¶
ExampleClient_SubscribeAs sets one mqttv5.Encoding on the client and names the payload type per subscription. PublishValue encodes any value and declares the Encoding's Content Type.
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/ashtonian/mqttv5"
)
// jsonEncoding is a tiny inline [mqttv5.Encoding] used by
// [ExampleClient_SubscribeAs]. In real code use the codec/json
// submodule's Encoding.
type jsonEncoding struct{}
func (jsonEncoding) ContentType() string { return "application/json" }
func (jsonEncoding) Marshal(v any) ([]byte, error) { return json.Marshal(v) }
func (jsonEncoding) Unmarshal(b []byte, v any) error { return json.Unmarshal(b, v) }
type reading struct {
Device string `json:"device"`
Temp float64 `json:"temp"`
}
type command struct {
Op string `json:"op"`
}
func main() {
cli, _ := mqttv5.New(
mqttv5.WithBroker("mqtt://localhost:1883"),
mqttv5.WithEncoding(jsonEncoding{}),
)
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
readings, _, err := cli.SubscribeAs[reading](ctx,
[]mqttv5.TopicFilter{{Topic: "sensors/+", QoS: 1}})
if err != nil {
return
}
commands, _, err := cli.SubscribeAs[command](ctx,
[]mqttv5.TopicFilter{{Topic: "devices/+/cmd", QoS: 1}})
if err != nil {
return
}
_ = cli.PublishValue(ctx,
mqttv5.PublishOptions{Topic: "sensors/a1", QoS: 1},
reading{Device: "a1", Temp: 22.5},
)
for {
select {
case m := <-readings:
fmt.Println(m.Topic, m.Value.Temp)
_ = m.Ack()
case m := <-commands:
fmt.Println(m.Topic, m.Value.Op)
_ = m.Ack()
}
}
}
Output:
func (*Client) SubscribeCallback ¶
func (c *Client) SubscribeCallback(ctx context.Context, filters []TopicFilter, h HandlerFunc, opts ...SubscribeOption) (SubscriptionToken, error)
SubscribeCallback registers a subscription that invokes h for every matching inbound PUBLISH.
h runs synchronously on the read goroutine, so keep it short: while it runs the connection reads nothing, and other subscriptions' messages and the acknowledgements of the client's own publishes wait behind it (the keep-alive does not take that wait for a dead broker). It must not call Client.Disconnect, which waits for the read goroutine: start it on another goroutine. The runtime auto-acks via Message.Ack after h returns; use Client.Subscribe or Client.SubscribeQueue when ack must be deferred past h.
Of the [SubscribeOption]s only SubZeroCopy applies; with it, the message's fields are valid only until h returns. Refusals, reconnects, and a call before Connect behave as for Client.Subscribe. Returns ErrNilHandler when h is nil.
Example ¶
ExampleClient_SubscribeCallback registers a synchronous handler invoked on the read goroutine. The handler must not block — a slow callback stalls PINGRESP processing and drops the connection. Ack fires automatically after the callback returns.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, _ := mqttv5.New(mqttv5.WithBroker("mqtt://localhost:1883"))
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
_, err := cli.SubscribeCallback(ctx,
[]mqttv5.TopicFilter{{Topic: "ctrl/+", QoS: 0}},
func(m *mqttv5.Message) {
fmt.Println(m.Topic, string(m.Payload))
},
)
if err != nil {
return
}
}
Output:
func (*Client) SubscribeCallbackAs ¶ added in v0.12.0
func (c *Client) SubscribeCallbackAs[T any](ctx context.Context, filters []TopicFilter, h func(*TypedMessage[T]), opts ...SubscribeOption) (SubscriptionToken, error)
SubscribeCallbackAs is Client.SubscribeCallback with each payload decoded into a T, as Client.SubscribeAs decodes it. h runs on the read goroutine and the message is acked when it returns.
func (*Client) SubscribeQueue ¶
func (c *Client) SubscribeQueue(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*Message], SubscriptionToken, error)
SubscribeQueue sends one SUBSCRIBE and returns a Queue of matching messages, capped at DefaultMaxSubscribeQueueSize unless WithMaxSubscribeQueueSize or SubMaxQueueSize say otherwise. On overflow, SubDropPolicy decides: DropNewest acks and drops the inbound message, except that QoS 1 and 2 messages have room for WithReceiveMaximum more beyond the cap, as on Client.Subscribe; DropOldest dequeues and acks the head, whatever its QoS, and admits the new one. Closes on Client.Unsubscribe or Client.Disconnect. Refusals, reconnects, and a call before Connect behave as for Client.Subscribe.
Example ¶
ExampleClient_SubscribeQueue stores matched messages in an unbounded queue. mqttv5.DropOldest evicts the queue head when SubMaxQueueSize is reached so the freshest N messages survive.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, _ := mqttv5.New(mqttv5.WithBroker("mqtt://localhost:1883"))
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
q, _, err := cli.SubscribeQueue(ctx,
[]mqttv5.TopicFilter{{Topic: "events/#", QoS: 1}},
mqttv5.SubMaxQueueSize(10_000),
mqttv5.SubDropPolicy(mqttv5.DropOldest),
)
if err != nil {
return
}
for {
m, ok := q.Dequeue(ctx)
if !ok {
break
}
fmt.Println(m.Topic, string(m.Payload))
_ = m.Ack()
}
}
Output:
func (*Client) SubscribeQueueAs ¶ added in v0.12.0
func (c *Client) SubscribeQueueAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*TypedMessage[T]], SubscriptionToken, error)
SubscribeQueueAs is Client.SubscribeQueue with each payload decoded into a T, as Client.SubscribeAs decodes it.
func (*Client) Unsubscribe ¶
func (c *Client) Unsubscribe(ctx context.Context, token SubscriptionToken) error
Unsubscribe ends the subscription: its channel or queue is closed before Unsubscribe returns, and an UNSUBSCRIBE is sent for every filter no other subscription of this client uses — the broker holds one subscription per filter, shared by all of them. Unsubscribe then waits for the broker's answer, across reconnects, until ctx ends; the UNSUBSCRIBE is still sent after that. Refusals are returned as an *UnsubscribeError.
type ClientGroup ¶
type ClientGroup struct {
// contains filtered or unexported fields
}
ClientGroup manages N parallel sessions to N independent brokers. Each member is a full Client with its own session and supervisor; the group adds dispatch policy on top. Use for bridges across independent brokers, multi-tenant SaaS with per-broker credentials, or devices split across independent clusters. For HA failover use WithBrokers on a single Client instead — ClientGroup does not fail over between members, though a member with several GroupMember.Brokers fails over among them. For publish throughput into one broker or cluster use WithPublisherPool: members of a group over one cluster would each receive every message a merged subscription to an unshared filter matches. A group is used once: after Disconnect its methods return ErrClosed.
func NewClientGroup ¶
func NewClientGroup(members []GroupMember, opts ...ClientGroupOption) (*ClientGroup, error)
NewClientGroup constructs a ClientGroup over the given members. Each GroupMember becomes a full *Client; the group applies the configured publish policy on top.
Member ClientIDs are kept unique: a member whose ClientID another already uses gets the first free "-N" suffix (N from 2); members without one get one each from their broker. Set GroupMember.Opts with WithClientID for explicit per-member IDs.
Defaults: GroupPublishBroadcast, parallel Connect/Disconnect.
Example ¶
ExampleNewClientGroup creates one mqttv5.Client per broker and dispatches via the configured GroupPublishPolicy. Subscribe merges inbound messages from every member into one channel.
For failover within one logical client (interchangeable brokers, same data), use mqttv5.WithBrokers on a single Client instead — see [Example_ClientSetBrokers].
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
group, err := mqttv5.NewClientGroup(
[]mqttv5.GroupMember{
{
Brokers: []string{"mqtts://emea.example.com:8883"},
Name: "emea",
Opts: []mqttv5.Option{mqttv5.WithCredentials("emea-svc", []byte("token-1"))},
},
{
Brokers: []string{"mqtts://apac.example.com:8883"},
Name: "apac",
Opts: []mqttv5.Option{mqttv5.WithCredentials("apac-svc", []byte("token-2"))},
},
},
mqttv5.WithGroupSharedOpts(
mqttv5.WithClientID("fanout"),
mqttv5.WithKeepAlive(30),
),
mqttv5.WithGroupPublishPolicy(mqttv5.GroupPublishBroadcast),
)
if err != nil {
panic(err)
}
ctx := context.Background()
if err := group.Connect(ctx); err != nil {
panic(err)
}
defer group.Disconnect(ctx)
msgs, _, err := group.Subscribe(ctx,
[]mqttv5.TopicFilter{{Topic: "events/#", QoS: 1}})
if err != nil {
return
}
for m := range msgs {
fmt.Println(m.Topic)
_ = m.Ack()
}
}
Output:
func (*ClientGroup) Connect ¶
func (g *ClientGroup) Connect(ctx context.Context) error
Connect dials every member. The default is parallel; use WithGroupSequentialLifecycle to serialise. The returned error is errors.Join of every member that failed — nil on full success. If at least one member succeeded the group is still usable for the healthy subset.
func (*ClientGroup) Disconnect ¶
func (g *ClientGroup) Disconnect(ctx context.Context) error
Disconnect tears down every member, which closes the merged channels and queues of the group's subscriptions. The default is parallel; use WithGroupSequentialLifecycle to serialise. Returned error is errors.Join of every member's Disconnect error. The group cannot be connected again.
func (*ClientGroup) Member ¶
func (g *ClientGroup) Member(name string) *Client
Member returns the Client for the named member, or nil when no such member exists.
func (*ClientGroup) Members ¶
func (g *ClientGroup) Members() []*Client
Members returns the underlying Clients in member order. Use for per-member operations the group API doesn't expose directly (Stats per member, manual Publish to a specific member, etc.).
func (*ClientGroup) Names ¶
func (g *ClientGroup) Names() []string
Names returns the member names in order.
func (*ClientGroup) Publish ¶
func (g *ClientGroup) Publish(ctx context.Context, opts PublishOptions) ([]GroupResult, error)
Publish dispatches opts across members per the configured GroupPublishPolicy and returns each member's part. Broadcast publishes to every member at once and fails with a *GroupError when fewer than WithGroupSuccess require succeed. RoundRobin and HashByTopic send the message once: they move to the next member only when one could not send it at all (not connected), so a broker refusal or ctx ending is returned as is and never duplicates the message.
func (*ClientGroup) PublishValue ¶ added in v0.12.0
func (g *ClientGroup) PublishValue(ctx context.Context, opts PublishOptions, v any) ([]GroupResult, error)
PublishValue encodes v with each member's Encoding and publishes it as ClientGroup.Publish does; members with different Encodings send differently encoded copies. If any member has no Encoding it returns ErrNoEncoding and publishes nothing.
func (*ClientGroup) Subscribe ¶
func (g *ClientGroup) Subscribe(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *Message, map[string]SubscriptionToken, error)
Subscribe subscribes on every member and merges their messages into one channel; Ack on a message goes back to the member that delivered it. The channel's buffer and drop rules are those of Client.Subscribe, applied as each member delivers, with room for every member's QoS 1 and 2 messages. It closes once every member's subscription has ended: after ClientGroup.UnsubscribeAll, an Unsubscribe of each member, or Disconnect.
When fewer members than WithGroupSuccess requires subscribe — or ctx ends — the members that did are unsubscribed again and a *GroupError (or ctx's error) is returned. Otherwise the token map, keyed by member name, holds the members that subscribed; failures of the others are logged. Called before ClientGroup.Connect, it returns at once, as Client.Subscribe does, and each member's SUBSCRIBE goes out first on its connection.
func (*ClientGroup) SubscribeAs ¶ added in v0.12.0
func (g *ClientGroup) SubscribeAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (<-chan *TypedMessage[T], map[string]SubscriptionToken, error)
SubscribeAs is ClientGroup.Subscribe with each payload decoded into a T by the delivering member's Encoding (or SubEncoding), as Client.SubscribeAs decodes it.
func (*ClientGroup) SubscribeQueue ¶
func (g *ClientGroup) SubscribeQueue(ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*Message], map[string]SubscriptionToken, error)
SubscribeQueue is the queue-merged variant of Subscribe, with the bound and drop policy of Client.SubscribeQueue applied to the merged queue as a whole.
func (*ClientGroup) SubscribeQueueAs ¶ added in v0.12.0
func (g *ClientGroup) SubscribeQueueAs[T any](ctx context.Context, filters []TopicFilter, opts ...SubscribeOption) (*Queue[*TypedMessage[T]], map[string]SubscriptionToken, error)
SubscribeQueueAs is ClientGroup.SubscribeQueue with each payload decoded into a T, as ClientGroup.SubscribeAs decodes it.
func (*ClientGroup) Unsubscribe ¶
func (g *ClientGroup) Unsubscribe(ctx context.Context, name string, token SubscriptionToken) error
Unsubscribe tears down the named member's subscription.
func (*ClientGroup) UnsubscribeAll ¶
func (g *ClientGroup) UnsubscribeAll(ctx context.Context, tokens map[string]SubscriptionToken) error
UnsubscribeAll tears down every token in the supplied map. Errors are joined.
type ClientGroupOption ¶
type ClientGroupOption func(*clientGroupConfig)
ClientGroupOption configures a ClientGroup at construct time.
func WithGroupPublishPolicy ¶
func WithGroupPublishPolicy(p GroupPublishPolicy) ClientGroupOption
WithGroupPublishPolicy selects the ClientGroup.Publish dispatch policy. See GroupPublishPolicy.
func WithGroupSequentialLifecycle ¶
func WithGroupSequentialLifecycle() ClientGroupOption
WithGroupSequentialLifecycle disables the default parallel Connect / Disconnect / Subscribe across members. Useful in tests where deterministic ordering matters more than wall time.
func WithGroupSharedOpts ¶
func WithGroupSharedOpts(opts ...Option) ClientGroupOption
WithGroupSharedOpts applies opts to every member before that member's own GroupMember.Opts. Use for shared settings (KeepAlive, Logger, ReconnectBackoff). Per-member Opts override.
func WithGroupSuccess ¶ added in v0.11.0
func WithGroupSuccess(s GroupSuccess) ClientGroupOption
WithGroupSuccess sets how many members must succeed for a broadcast Publish or a Subscribe to succeed. Default GroupSuccessAll.
type Config ¶
type Config struct {
// BrokerURLs is the ordered list of broker URLs. The supervisor
// rotates through it on reconnect; [Client.SetBrokers] replaces
// it at runtime.
BrokerURLs []string
ClientID string
Username string
Password []byte
KeepAlive uint16
// CleanStart sets the CleanStart flag on the initial CONNECT
// (§3.1.2.4). When not set explicitly it is true unless there is
// session state to resume (see [WithStore]).
CleanStart bool
// SessionLossPolicy decides what happens to unacknowledged QoS 1/2
// publishes when a connection starts without the old session.
// Default [SessionLossRepublish].
SessionLossPolicy SessionLossPolicy
// OutboundTopicAliases replaces repeated QoS 0 topics with topic
// aliases. Default false.
OutboundTopicAliases bool
// LenientDecoding tolerates the harmless broker violations listed at
// [WithLenientDecoding] instead of disconnecting. Default false.
LenientDecoding bool
// OnResubscribeError fires when the broker refuses filters the client
// re-subscribes after a session loss. Runs with the other lifecycle callbacks (see the package documentation).
OnResubscribeError func(SubscriptionToken, error)
// QoSDowngrade lowers a publish's QoS to the broker's Maximum QoS
// instead of failing it with [ErrQoSNotSupported]. Default false.
QoSDowngrade bool
// OnServerRedirect fires when the broker redirects the client (§4.11).
OnServerRedirect func(ServerRedirect)
// FollowServerRedirects makes the client connect where a redirect
// points.
FollowServerRedirects bool
// RetryInitialConnect makes Connect hand a failed first handshake
// to the supervisor's reconnect loop instead of returning its error.
RetryInitialConnect bool
// CleanStartOnReconnect sets CleanStart on every CONNECT after
// the first. Default false (preserve broker session for QoS 1/2
// resume).
CleanStartOnReconnect bool
SessionExpiry uint32
ReceiveMaximum uint16
// MaximumPacketSize caps the largest packet the broker may send
// to this client (§3.1.2.11.4). Zero advertises no limit. A larger
// packet closes the connection with DISCONNECT 0x95.
MaximumPacketSize uint32
// ReadBufferSize is the per-connection read window in bytes: how
// much one read syscall can bring in. Default
// [DefaultReadBufferSize].
ReadBufferSize int
// InboundTopicAliasMaximum is the inbound TopicAliasMaximum
// (§3.1.2.11.5). Zero (default) tells the broker not to alias
// inbound PUBLISHes. The outbound budget is taken automatically
// from the broker's CONNACK.
InboundTopicAliasMaximum uint16
// RequestResponseInformation asks the broker for
// ResponseInformation in CONNACK (§3.1.2.11.6).
RequestResponseInformation bool
// RequestProblemInformation asks the broker for ReasonString /
// UserProperties on error responses (§3.1.2.11.7).
RequestProblemInformation bool
// ConnectUserProperties are sent verbatim as CONNECT user
// properties.
ConnectUserProperties []wire.UserProperty
ConnectTimeout time.Duration
PingTimeout time.Duration
// DisconnectFlushTimeout bounds the wait for a graceful
// DISCONNECT write before the socket is forcibly closed.
// Default 500ms.
DisconnectFlushTimeout time.Duration
WriteQueueSize int
// WriteOverflowPolicy controls what QoS 0 Publish does when the
// writer queue is full. WriteBlock (default) waits for room or
// ctx; WriteDropNewest returns ErrWriteQueueFull immediately.
// QoS 1/2 unaffected.
WriteOverflowPolicy WriteOverflowPolicy
// WriteBatchMax caps how many pre-encoded packets the writer
// goroutine coalesces into one writev. 0 disables batching. See
// [WithWriteBatch].
WriteBatchMax int
WillMessage *WillOptions
// MaxSubscribeQueueSize caps each SubscribeQueue's length.
// 0 means [DefaultMaxSubscribeQueueSize]; [UnboundedQueue] (any
// negative value) removes the cap.
MaxSubscribeQueueSize int
// DropPolicy sets the default policy for full subscription
// buffers. [Client.Subscribe] (chan) supports DropNewest only;
// [Client.SubscribeQueue] honors both.
DropPolicy DropPolicy
// PublisherPoolSize > 0 enables a pool of that many publish-only
// connections, which falls back to the main connection when no
// member can send.
PublisherPoolSize int
// PublisherPoolRouting selects the pool member-picking strategy.
PublisherPoolRouting PoolRoutingPolicy
// PublisherPoolClientIDFn derives each pool member's ClientID
// from the parent ClientID and a 1-based index. Default is
// fmt.Sprintf("%s-pub-%d", parent, idx), or empty (broker-assigned)
// when the parent's is empty.
PublisherPoolClientIDFn func(parent string, idx int) string
// PublishMode selects QoS 0 write-completion semantics. QoS 1/2
// always wait for the broker's ack.
PublishMode PublishMode
// Encoding encodes and decodes the payloads of the typed calls
// (PublishValue, SubscribeAs and their variants). See [WithEncoding].
Encoding Encoding
// OnConnectionUp fires on each successful CONNECT/CONNACK with
// what the broker granted, possibly after Connect has returned.
// Runs with the other lifecycle callbacks (see the package documentation).
OnConnectionUp func(ConnackInfo)
// OnConnectionDown fires on unexpected connection loss (not on
// user-initiated Disconnect). Return false to stop the
// supervisor, which waits for the answer; a subsequent Connect
// restarts it. Runs with the other lifecycle callbacks (see the package documentation).
OnConnectionDown func() bool
// OnConnectError fires per failed CONNECT attempt (dial failure,
// CONNACK refusal, AUTH-loop error). Observability only; the
// supervisor retries regardless. Runs with the other lifecycle callbacks (see the package documentation).
OnConnectError func(err error)
// OnReconnectAttempt fires before each reconnect dial (not the
// initial Connect), which waits for it. attempt starts at 1.
// Runs with the other lifecycle callbacks (see the package documentation).
OnReconnectAttempt func(attempt int, brokerURL string)
// StatsEnabled toggles the [Client.Stats] counter set. Default
// false — when disabled the hot path skips every atomic
// increment and Stats returns the zero value.
StatsEnabled bool
// OnServerDisconnect fires when the broker sends a DISCONNECT
// (§3.14).
// Fires after the connection is marked down and before
// OnConnectionDown. May call [Client.SetBrokers] to honour a
// ServerMoved / UseAnotherServer redirect on the next attempt.
// Runs with the other lifecycle callbacks (see the package documentation).
OnServerDisconnect func(DisconnectInfo)
// Authenticator drives MQTT v5 enhanced authentication when set.
Authenticator Authenticator
// OnReauthenticated fires when a re-authentication (§4.12) the
// client started with Reauthenticate concludes successfully (broker
// AUTH 0x00 Success), possibly after Reauthenticate has returned.
// Observability only: the success is also Reauthenticate's return
// value. Runs with the other lifecycle callbacks (see the package documentation).
OnReauthenticated func()
// ConnectPacketBuilder is invoked immediately before each CONNECT
// is serialised and may change its credentials and user
// properties — typically rotating Username/Password per attempt
// for OAuth refresh. ctx is the per-attempt context bounded by
// ConnectTimeout. A non-nil error fails the attempt; the
// supervisor retries after backoff and fires OnConnectError.
ConnectPacketBuilder func(ctx context.Context, opts *ConnectOptions) error
// ReconnectBackoff returns the delay before retry attempt N.
// Defaults to [DefaultReconnectBackoff].
ReconnectBackoff Backoff
Logger *slog.Logger
// Store persists session state across process restarts. Nil (the
// default) keeps it in memory: in-flight QoS 1/2 messages survive
// reconnects but not a restart.
Store session.Store
// OnStoreFailure fires once when a Store write fails and the client
// has stopped because of it (see [WithStore]). Runs with the other lifecycle callbacks (see the package documentation).
OnStoreFailure func(err error)
TLSConfig *tls.Config
Dialer *net.Dialer
// DialFunc replaces the built-in TCP/TLS dial path. Takes
// precedence over TLSConfig and Dialer. Use for SOCKS / mTLS /
// WebSocket (see transport/ws) / in-memory test transports.
// When set, broker URL scheme validation is skipped.
DialFunc transport.DialFunc
// contains filtered or unexported fields
}
Config carries the resolved configuration for a Client. Construct via New.
type ConnackInfo ¶ added in v0.11.0
type ConnackInfo struct {
// Broker is the URL of the broker the connection reached: with
// [WithBrokers], the one the client failed over to.
Broker string
// SessionPresent reports whether the broker resumed an existing
// session.
SessionPresent bool
// SessionExpiry is the effective Session Expiry Interval in seconds:
// the broker's value when it overrode the requested one.
SessionExpiry uint32
// KeepAlive is the effective keep-alive in seconds: the broker's
// Server Keep Alive when sent, otherwise the requested value.
// Zero disables keep-alive.
KeepAlive uint16
// ReceiveMaximum caps the QoS 1/2 PUBLISHes the client may have
// unacknowledged (default 65,535).
ReceiveMaximum uint16
// MaximumQoS is the highest QoS the broker accepts (default 2).
MaximumQoS byte
// RetainAvailable reports whether retained messages are accepted
// (default true).
RetainAvailable bool
// MaximumPacketSize is the largest packet the broker accepts, in
// bytes; zero means no limit beyond the protocol's.
MaximumPacketSize uint32
// TopicAliasMaximum is the number of outbound topic aliases the
// broker accepts (default 0: none).
TopicAliasMaximum uint16
// AssignedClientID is the identifier the broker chose when the
// client connected without one.
AssignedClientID string
// Subscription features (default true when not sent).
WildcardSubscriptionAvailable bool
SubscriptionIdentifiersAvailable bool
ResponseInformation string
ServerReference string
ReasonString string
UserProperties []wire.UserProperty
AuthenticationMethod string
AuthenticationData []byte
}
ConnackInfo is what the broker granted in CONNACK (§3.2.2.3), with the spec's defaults applied to properties it did not send. The client enforces these limits on everything it sends: a publish that exceeds them fails before it reaches the wire.
type ConnectOptions ¶ added in v0.11.0
type ConnectOptions struct {
Username string
Password []byte
UserProperties []UserProperty
}
ConnectOptions are the parts of a CONNECT that WithConnectPacketBuilder may change before each attempt, typically to present a fresh credential.
type ContextAuthenticator ¶ added in v0.11.0
type ContextAuthenticator interface {
ContinueContext(ctx context.Context, brokerData []byte) (response []byte, done bool, err error)
}
ContextAuthenticator is an optional interface an Authenticator may implement so answering a broker challenge can be cancelled. The client then calls ContinueContext instead of Continue, with a ctx bounded by the connect timeout. Mechanisms that do I/O to answer (a token service, a hardware key) should implement it.
type DisconnectInfo ¶ added in v0.11.0
type DisconnectInfo struct {
ReasonCode ReasonCode
ReasonString string
// ServerReference names another server to use, with reason 0x9C
// Use another server or 0x9D Server moved.
ServerReference string
UserProperties []UserProperty
}
DisconnectInfo is a DISCONNECT the broker sent.
type DisconnectOptions ¶ added in v0.11.0
type DisconnectOptions struct {
ReasonCode ReasonCode // 0x00 Normal disconnection, 0x04 with Will, or an error code
// SessionExpiryInterval replaces the value CONNECT set; it cannot
// be non-zero if CONNECT sent 0 [MQTT-3.14.2-2].
SessionExpiryInterval *uint32
ReasonString string
UserProperties []UserProperty
}
DisconnectOptions is the DISCONNECT Client.DisconnectWith sends.
type DropPolicy ¶
type DropPolicy uint8
DropPolicy controls behaviour when a bounded subscription buffer fills.
const ( // DropNewest discards the inbound message + acks it so the // broker doesn't retry. DropNewest DropPolicy = iota // DropOldest evicts the buffer head to admit the inbound // message. SubscribeQueue only — chan-based [Client.Subscribe] // rejects this with [ErrChanDropOldestUnsupported]. DropOldest )
func (DropPolicy) String ¶
func (p DropPolicy) String() string
String returns a stable debug name for the policy.
type Encoding ¶ added in v0.12.0
type Encoding interface {
// ContentType is the MQTT 5 Content Type a typed publish declares,
// such as "application/json". Empty declares none.
ContentType() string
// Marshal encodes v.
Marshal(v any) ([]byte, error)
// Unmarshal decodes data into v, a non-nil pointer. It must not
// keep data, or slices of it, once it returns: on a [SubZeroCopy]
// subscription data is the network frame, reused after the message
// is acked.
Unmarshal(data []byte, v any) error
}
Encoding turns Go values into payloads and back for the typed calls: Client.PublishValue and Client.SubscribeAs with their queue, callback, ClientGroup and QueuePublisher variants. One Encoding serves every payload type, so a client sets it once (WithEncoding) and names the type per call. The codec/json and codec/msgpack submodules provide one each. Implementations must be safe for concurrent use.
type GroupError ¶ added in v0.11.0
type GroupError struct {
Op string // "publish" or "subscribe"
Need int
Results []GroupResult
}
GroupError reports a group operation that fewer members completed than WithGroupSuccess requires. errors.Is and errors.As see every member's error.
func (*GroupError) Error ¶ added in v0.11.0
func (e *GroupError) Error() string
func (*GroupError) Unwrap ¶ added in v0.11.0
func (e *GroupError) Unwrap() []error
type GroupMember ¶
type GroupMember struct {
// Brokers are the URLs this member dials: one, or several the
// member fails over between as a [Client] with [WithBrokers] does.
// Required; neither Opts nor [WithGroupSharedOpts] may set brokers.
Brokers []string
// Name is an optional identifier surfaced in logs, returned in
// the Subscribe token map, and accepted by ClientGroup.Member.
// Defaults to "member-N" (1-based) when empty.
Name string
// Opts are applied AFTER any WithGroupSharedOpts shared options.
// Use to override or extend the shared opts per-broker (auth,
// TLS, ClientID, per-member callbacks, etc.).
Opts []Option
}
GroupMember describes one member of a ClientGroup. Each member becomes a full Client with its own session, supervisor, and CONNECT identity.
Use Opts for per-member settings — WithCredentials, WithTLSConfig, WithAuthenticator, WithClientID, WithConnectPacketBuilder, a per-member WithOnConnectionUp closing over Name for identity, etc. Opts are applied AFTER WithGroupSharedOpts so per-member settings win.
type GroupPublishPolicy ¶
type GroupPublishPolicy uint8
GroupPublishPolicy selects how ClientGroup.Publish dispatches across members. For HA failover across interchangeable brokers use WithBrokers on a single Client instead — ClientGroup keeps N parallel sessions where every member is treated as itself.
const ( // GroupPublishBroadcast (default) publishes to every member at // once. Use when members carry different data (bridge / mirror // semantic). [WithGroupSuccess] decides how many must succeed. GroupPublishBroadcast GroupPublishPolicy = iota // GroupPublishRoundRobin spreads publishes over the group's // brokers, one member per call. The per-call starting member // advances by one; a member that cannot send (not connected) passes // the message to the next. Use when any of the brokers will do for // a message because its consumers subscribe to all of them. For // publish throughput into one broker or cluster use // [WithPublisherPool] instead. GroupPublishRoundRobin // GroupPublishHashByTopic picks the member by FNV-1a of // opts.Topic, preserving per-topic ordering across the group while // every member is connected; a member that cannot send passes the // message to the next, briefly reordering that topic. Empty Topic // collapses to member 0. GroupPublishHashByTopic )
func (GroupPublishPolicy) String ¶
func (p GroupPublishPolicy) String() string
String returns a stable debug name for the policy.
type GroupResult ¶ added in v0.11.0
GroupResult is one member's part in a group operation.
type GroupSuccess ¶ added in v0.11.0
type GroupSuccess int
GroupSuccess is how many members must succeed for a broadcast ClientGroup.Publish or a ClientGroup.Subscribe to succeed.
const ( // GroupSuccessAll (default) needs every member. GroupSuccessAll GroupSuccess = 0 // GroupSuccessAny needs one member. GroupSuccessAny GroupSuccess = 1 )
func GroupSuccessQuorum ¶ added in v0.11.0
func GroupSuccessQuorum(n int) GroupSuccess
GroupSuccessQuorum needs n members.
type HandlerFunc ¶
type HandlerFunc func(*Message)
HandlerFunc runs synchronously on the read goroutine for each matching inbound PUBLISH. Keep it short: while it runs the connection reads nothing, so other subscriptions' messages and the acknowledgements of the client's own publishes wait behind it. The keep-alive does not take that wait for a dead broker. Use Subscribe / SubscribeQueue for work that may take long.
type MemoryPublisherQueue ¶
type MemoryPublisherQueue struct {
// contains filtered or unexported fields
}
MemoryPublisherQueue is an in-memory PublisherQueue: entries do not survive the process. Use the queue/file submodule to keep them across restarts.
func NewMemoryPublisherQueue ¶
func NewMemoryPublisherQueue() *MemoryPublisherQueue
NewMemoryPublisherQueue returns an empty in-memory queue.
func (*MemoryPublisherQueue) Ack ¶
func (q *MemoryPublisherQueue) Ack(_ context.Context, seq uint64) error
Ack implements PublisherQueue.
func (*MemoryPublisherQueue) Close ¶
func (q *MemoryPublisherQueue) Close() error
Close implements PublisherQueue.
func (*MemoryPublisherQueue) Enqueue ¶
func (q *MemoryPublisherQueue) Enqueue(_ context.Context, e QueueEntry, limit QueueLimit) (uint64, []QueueEntry, error)
Enqueue implements PublisherQueue.
func (*MemoryPublisherQueue) Len ¶
func (q *MemoryPublisherQueue) Len(context.Context) (int, error)
Len implements PublisherQueue.
func (*MemoryPublisherQueue) Peek ¶ added in v0.11.0
func (q *MemoryPublisherQueue) Peek(_ context.Context, after uint64, n int) ([]QueueEntry, error)
Peek implements PublisherQueue.
type Message ¶
type Message struct {
Topic string
Payload []byte
Properties Properties
QoS byte
Retain bool
Dup bool
PacketID uint16
// contains filtered or unexported fields
}
Message is one inbound PUBLISH as seen by one subscription handler.
By default a Message owns its memory: Topic, Payload and Properties are copied out of the network frame before any handler runs, stay valid for as long as the Message is reachable, and may be retained freely. When a PUBLISH matches several subscriptions, each handler receives its own *Message; the handles share the same read-only bytes.
Subscriptions created with SubZeroCopy instead receive Messages whose Topic, Payload and Properties alias the pooled network frame. Those fields are valid only until every handler's Ack has returned.
Ack acknowledges the message to the broker (PUBACK for QoS 1, PUBREC for QoS 2, emitted in arrival order per §4.6) once every handler that received the PUBLISH has acked. Ack is idempotent per Message: later calls are no-ops and never affect any other message.
func (*Message) Ack ¶
Ack acknowledges the message for this handler. It is safe to call more than once and from any goroutine; only the first call counts. For QoS 0 messages Ack is only needed on zero-copy subscriptions, where it returns the network frame to the pool.
func (*Message) ClonePayload ¶
ClonePayload returns a copy of Payload. Only needed on zero-copy subscriptions; owned messages may simply keep m.Payload.
func (*Message) CloneTopic ¶
CloneTopic returns a copy of Topic. Only needed on zero-copy subscriptions; owned messages may simply keep m.Topic.
func (*Message) PublishOptions ¶ added in v0.11.0
func (m *Message) PublishOptions() PublishOptions
PublishOptions returns options that publish m again as it arrived: topic, payload, QoS, Retain, and the properties a client may send — Payload Format Indicator, the Message Expiry Interval the broker left it, Content Type, Response Topic, Correlation Data and User Properties. A bridge changes the Topic or QoS and publishes it on another connection. Retain is the flag as delivered: the publisher's only on a subscription with RetainAsPublished. The options share m's memory, so on a zero-copy subscription publish them before acking m.
type Option ¶
Option mutates a Config during New; a non-nil return aborts construction.
func WithAuthenticator ¶
func WithAuthenticator(a Authenticator) Option
WithAuthenticator enables MQTT v5 enhanced authentication. When combined with WithPublisherPool, the same Authenticator runs concurrently across the parent and every pool member; implementations used that way must be safe for concurrent use.
func WithBroker ¶
WithBroker sets a single broker URL. Supported schemes: mqtt://, tcp://, mqtts://, tls://, ssl://; default ports 1883 / 8883 fill in when missing. For ws:// or wss:// pair with WithDialFunc.
func WithBrokers ¶
WithBrokers sets the broker URL list for failover. Each client starts at a random entry, so a fleet spreads across the list. Client.Connect tries the next entry when one fails; each reconnect attempt takes the next entry, so the first one leaves the broker that just dropped. The client stays on whichever broker accepts it. Client.SetBrokers replaces the list at runtime.
Brokers that do not share session state answer a failover CONNECT with Session Present = 0: unacknowledged QoS 1/2 publishes then follow WithSessionLossPolicy and subscriptions are re-issued.
func WithCleanStart ¶
WithCleanStart sets the CleanStart flag on the initial CONNECT. Without it the first CONNECT starts clean unless there is session state to resume — state left by an earlier Connect on this Client or loaded from WithStore — in which case it resumes (CleanStart=0). WithCleanStart(true) always starts a new session and discards that state. Reconnects use WithCleanStartOnReconnect.
func WithCleanStartOnReconnect ¶
WithCleanStartOnReconnect sets CleanStart on every CONNECT after the first. Default false — preserve broker session for QoS 1/2 resume.
func WithClientID ¶
WithClientID sets the MQTT ClientID. Empty string asks the broker to assign one via the AssignedClientIdentifier CONNACK property.
func WithConnectPacketBuilder ¶
func WithConnectPacketBuilder(fn func(ctx context.Context, opts *ConnectOptions) error) Option
WithConnectPacketBuilder lets fn change the CONNECT's credentials and user properties immediately before each attempt — canonical OAuth token-refresh hook. ctx is the per-attempt context bounded by ConnectTimeout. A non-nil error fails the attempt; the supervisor retries after backoff and fires OnConnectError.
func WithConnectTimeout ¶
WithConnectTimeout caps the CONNECT handshake (dial + CONNECT/CONNACK).
func WithConnectUserProperties ¶
func WithConnectUserProperties(p []wire.UserProperty) Option
WithConnectUserProperties replaces the CONNECT user-property list. The slice is referenced as-is; do not mutate after passing.
func WithConnectUserProperty ¶
WithConnectUserProperty appends one CONNECT user property. May be called multiple times; for bulk replacement see WithConnectUserProperties.
func WithCredentials ¶
WithCredentials sets username and password sent in the CONNECT packet.
func WithDialFunc ¶
WithDialFunc replaces the built-in TCP/TLS dial path. Takes precedence over WithDialer / WithTLSConfig. Use for SOCKS / HTTP proxies, per-dial mTLS, WebSocket (transport/ws), or in-memory test transports. ctx is bounded by ConnectTimeout. When set, broker URL scheme validation is skipped. Nil rejected.
The transport/ws sibling submodule exposes a [ws.DialFunc] helper that bakes in DialOpts (TLS config, custom headers, etc.) and returns a value directly usable here:
mqttv5.WithDialFunc(ws.DialFunc(ws.DialOpts{TLSConfig: tlsCfg}))
func WithDialer ¶
WithDialer overrides the default *net.Dialer.
func WithDisconnectFlushTimeout ¶
WithDisconnectFlushTimeout caps the wait for a graceful DISCONNECT write before the socket is forcibly closed. Default DefaultDisconnectFlushTimeout.
func WithDropPolicy ¶
func WithDropPolicy(p DropPolicy) Option
WithDropPolicy sets the default per-subscription drop policy. Per-call override via SubDropPolicy; see DropPolicy.
func WithEncoding ¶ added in v0.12.0
WithEncoding sets the Encoding of the client's typed calls. A subscription can use another with SubEncoding.
func WithFollowServerRedirects ¶ added in v0.11.0
func WithFollowServerRedirects() Option
WithFollowServerRedirects makes the client connect where the broker redirects it: after 0x9D Server moved the reference replaces the broker list (Client.SetBrokers); after 0x9C Use another server only the next connection attempt uses it. A reference that is a host or host:port keeps the current URL's scheme and path. Off by default: a redirect is reported (WithOnServerRedirect) and otherwise ignored.
func WithInboundTopicAliasMaximum ¶
WithInboundTopicAliasMaximum advertises how many inbound topic aliases this client accepts (§3.1.2.11.5). Zero (default) disables inbound aliasing.
func WithKeepAlive ¶
WithKeepAlive sets the keepalive interval (seconds, range 1..65535). Pass 0 to WithoutKeepAlive instead.
func WithLenientDecoding ¶ added in v0.11.0
func WithLenientDecoding() Option
WithLenientDecoding tolerates two harmless protocol violations seen from real brokers — a non-minimally encoded Remaining Length and reserved flag bits on PINGRESP — logging each at Warn instead of disconnecting. Every other violation still closes the connection with the spec's reason code (0x81 Malformed Packet, 0x82 Protocol Error, or a more specific one) and counts in Stats.ProtocolErrors.
func WithLogger ¶
WithLogger sets the *slog.Logger. Default slog.Default.
func WithMaxSubscribeQueueSize ¶
WithMaxSubscribeQueueSize caps each Client.SubscribeQueue's length. Default DefaultMaxSubscribeQueueSize; UnboundedQueue removes the cap. When a queue is full its DropPolicy applies. Per-call override via SubMaxQueueSize.
func WithMaximumPacketSize ¶
WithMaximumPacketSize caps the largest packet the broker may send to this client (§3.1.2.11.4). Zero advertises no limit. A broker that sends a larger packet is disconnected with reason 0x95.
func WithOnConnectError ¶
WithOnConnectError fires per failed CONNECT attempt (dial err, CONNACK refusal, AUTH-loop err). Observability only — the supervisor retries regardless. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnConnectionDown ¶
WithOnConnectionDown registers a callback fired when the connection is lost. Does NOT fire on user-initiated Disconnect. Return false to terminate the supervisor — no further reconnect attempts; the supervisor waits for the answer. A subsequent Connect on the same Client re-starts the lifecycle. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnConnectionUp ¶
func WithOnConnectionUp(fn func(ConnackInfo)) Option
WithOnConnectionUp fires on each successful CONNECT/CONNACK with what the broker granted (session present, limits, assigned ClientID, effective keep-alive, ...); Client.ServerInfo returns the same for the current connection. It may run after Client.Connect has returned. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnReauthenticated ¶ added in v0.10.0
func WithOnReauthenticated(fn func()) Option
WithOnReauthenticated registers a callback fired when a re-authentication (§4.12) the client started with Reauthenticate concludes successfully (broker AUTH 0x00 Success), possibly after Reauthenticate has returned. Observability only. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnReconnectAttempt ¶
WithOnReconnectAttempt fires before each reconnect dial (not the initial Client.Connect), which waits for it. attempt starts at 1 after a connection that lasted, and keeps counting across connections that dropped sooner than the reconnect delay before them (see WithReconnectBackoff). Runs with the other lifecycle callbacks (see the package documentation).
func WithOnResubscribeError ¶ added in v0.11.0
func WithOnResubscribeError(fn func(SubscriptionToken, error)) Option
WithOnResubscribeError observes filters the broker refuses when the client re-subscribes after a session loss (Session Present = 0). A subscription that loses every filter closes, and its token's Err reports why; one that keeps some carries on with those. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnServerDisconnect ¶
func WithOnServerDisconnect(fn func(DisconnectInfo)) Option
WithOnServerDisconnect fires when the broker sends a DISCONNECT (§3.14), with its reason, reason string, server reference and user properties. Fires after the connection is marked down and before OnConnectionDown; may call Client.SetBrokers to honour a ServerMoved / UseAnotherServer redirect on the next attempt, which waits for it. Not fired for socket-level errors or client-initiated Disconnect. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnServerRedirect ¶ added in v0.11.0
func WithOnServerRedirect(fn func(ServerRedirect)) Option
WithOnServerRedirect calls fn when the broker refuses a CONNECT, or ends the connection, with reason 0x9C Use another server or 0x9D Server moved and a Server Reference (§4.11). May call Client.SetBrokers: the next attempt waits for it. Runs with the other lifecycle callbacks (see the package documentation).
func WithOnStoreFailure ¶ added in v0.11.0
WithOnStoreFailure registers a callback fired once when a session store write failed and the client has stopped because of it (see WithStore); err is a *StoreError. Call Client.Connect, from the callback or later, to start again from what the store holds. Runs with the other lifecycle callbacks (see the package documentation).
func WithOutboundTopicAliases ¶ added in v0.11.0
func WithOutboundTopicAliases() Option
WithOutboundTopicAliases makes QoS 0 publishes replace a repeated topic with a topic alias (§3.3.2.3.4) while the broker's Topic Alias Maximum lasts: the first publish to a topic registers the alias, later ones send only the alias. It saves bandwidth for long topics. QoS 1/2 publishes always carry their topic, since they may be resent on a connection where the alias is unknown. Off by default.
func WithPingTimeout ¶
WithPingTimeout sets how long the client waits for any packet after a PINGREQ before declaring the connection dead and reconnecting. It must be shorter than the keep-alive (New rejects it otherwise); when the broker grants a shorter keep-alive than requested, half the granted keep-alive is used instead. Default: the smaller of DefaultPingTimeout and half the keep-alive. Raise it only on links whose round trip exceeds a few seconds (satellite, congested cellular).
func WithPublishMode ¶
func WithPublishMode(m PublishMode) Option
WithPublishMode selects QoS 0 Client.Publish semantics. See PublishMode. QoS 1/2 unaffected.
func WithPublisherPool ¶
WithPublisherPool opens size publish-only connections, beside the main one, to the client's own brokers; Client.Publish spreads over them, and falls back to the main connection when none can send. 0 (the default) disables the pool. Each member connects with Session Expiry 0 (and Clean Start the first time), so its session ends with its connection: a QoS 1/2 publish interrupted by a member's reconnect follows the session-loss policy (sent again, possibly duplicated). Subscriptions, the Will, the session store and QueuePublisher stay on the main connection; members read their own acknowledgements, so a slow Client.SubscribeCallback handler does not delay pooled publishes. The broker sees each member as a client of its own (see WithPublisherPoolClientIDFn), so No Local does not hide a pooled publish from the main connection's subscriptions.
Example ¶
ExampleWithPublisherPool dedicates N publish-only connections to the same broker, lifting publish throughput above what one keep-alive socket can absorb. Pair with mqttv5.PoolRoutingHashByTopic to preserve per-topic ordering.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, err := mqttv5.New(
mqttv5.WithBroker("mqtt://localhost:1883"),
mqttv5.WithPublisherPool(4),
mqttv5.WithPublisherPoolRouting(mqttv5.PoolRoutingHashByTopic),
)
if err != nil {
panic(err)
}
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
// Round-robin / hashed across the 4 pool members under the hood.
for i := range 100 {
_ = cli.Publish(ctx, mqttv5.PublishOptions{
Topic: fmt.Sprintf("events/tick/%d", i%4),
Payload: fmt.Appendf(nil, `{"n":%d}`, i),
QoS: 1,
})
}
}
Output:
func WithPublisherPoolClientIDFn ¶
WithPublisherPoolClientIDFn derives each pool member's ClientID from the parent's configured ClientID and a 1-based index. The default is "<parent>-pub-<n>" (DefaultPublisherPoolClientIDFormat), or, for a parent without a ClientID, empty, so the broker assigns each member's. Broker ACLs or policies keyed on the ClientID must admit these IDs, and fn must give IDs no other client uses.
func WithPublisherPoolRouting ¶
func WithPublisherPoolRouting(p PoolRoutingPolicy) Option
WithPublisherPoolRouting selects the publisher-pool member-picking strategy. See PoolRoutingPolicy. No-op without a pool.
func WithQoSDowngrade ¶ added in v0.11.0
func WithQoSDowngrade() Option
WithQoSDowngrade sends a publish whose QoS is above the broker's Maximum QoS (CONNACK, §3.2.2.3.4) at that maximum instead of failing it with ErrQoSNotSupported. It weakens the delivery guarantee the caller asked for — QoS 2 becomes at-least-once — so it is off by default. Brokers such as AWS IoT Core advertise Maximum QoS 1.
func WithReadBufferSize ¶ added in v0.11.0
WithReadBufferSize sets the per-connection read window in bytes: how much one read syscall can bring in. A larger window cuts syscalls for streams of small messages at the cost of that much memory per connection. Default DefaultReadBufferSize.
func WithReceiveMaximum ¶
WithReceiveMaximum sets the Receive Maximum (§3.1.2.11.3): how many QoS 1/2 messages the broker may send that the client has not yet acknowledged. A delivered message is acknowledged once the application acks it, so this is also how many QoS 1/2 messages a subscription whose consumer falls behind can hold beyond its buffer, and what that costs in memory (see Client.Subscribe). A broker that sends more is disconnected with reason 0x93. Default DefaultReceiveMaximum; 0 keeps the default.
func WithReconnectBackoff ¶
WithReconnectBackoff sets the supervisor's reconnect backoff: the delay before each reconnect attempt, by attempt number. The count starts over only once a connection has outlived the delay before it, so a broker that drops connections right after accepting them is retried at the growing interval rather than at the first one. See ConstantBackoff and ExponentialBackoff; default DefaultReconnectBackoff.
func WithRequestProblemInformation ¶
WithRequestProblemInformation asks the broker for ReasonString / UserProperties on error responses (§3.1.2.11.7). Default true — debugging a CONNECT or PUBLISH refusal without these is "why was this rejected?" with no answer; the wire cost is a handful of bytes on the error path only. Pass false to opt out: the CONNECT then carries Request Problem Information = 0.
func WithRequestResponseInformation ¶
WithRequestResponseInformation asks the broker for ResponseInformation in CONNACK (§3.1.2.11.6) — used by the request/response pattern (§4.10).
func WithRetryInitialConnect ¶ added in v0.11.0
func WithRetryInitialConnect() Option
WithRetryInitialConnect lets the client start while the broker is unreachable: when the first CONNECT fails, Connect reports the error to WithOnConnectError, returns nil, and the supervisor retries with WithReconnectBackoff as after a lost connection. Publish and Subscribe return ErrNotConnected until then (QueuePublisher queues); Client.AwaitConnection waits for the connection. Without it, Connect returns the failure: with WithBrokers, once every broker has failed.
func WithSessionExpiry ¶
WithSessionExpiry sets the Session Expiry Interval (§3.1.2.11.2) in seconds. Default DefaultSessionExpiry (300 s / 5 min) — enough for QoS 1/2 resume across a typical reconnect blip. Zero ends the session with the network connection (no broker-side retention).
func WithSessionLossPolicy ¶ added in v0.11.0
func WithSessionLossPolicy(p SessionLossPolicy) Option
WithSessionLossPolicy sets the SessionLossPolicy. Default SessionLossRepublish.
func WithStats ¶
func WithStats() Option
WithStats enables the Client.Stats counter set. When disabled the hot path skips every atomic increment and Stats returns the zero value. Off by default; enable to scrape counters into Prometheus / OpenTelemetry / expvar.
func WithStore ¶
WithStore persists session state in s so a restarted process resumes its QoS 1/2 flows: unacknowledged publishes are resent, QoS 2 exchanges continue at the phase they reached, and a broker-assigned ClientID is reused. Each state change is written to s before the packet that depends on it is sent (see package session). Without a store the same state is kept in memory and survives reconnects only.
A failed write of a new message's record fails that Publish. Any other failed write means the store no longer holds what the session has done, so the client stops rather than go on without the guarantee the store exists for: it sends DISCONNECT 0x80 (the broker publishes the Will), ends as Client.Disconnect would, and fires WithOnStoreFailure. QoS 1/2 Publish calls still waiting return the failure, a *StoreError matching ErrStoreFailed; their messages stay in the store. The next Client.Connect reloads the session from s, as a restarted process would.
The client does not close s. Use the store/file submodule for a file-backed store, or session.MemoryStore in tests.
func WithTLSConfig ¶
WithTLSConfig supplies a *tls.Config for mqtts:// / tls:// dials. Ignored for plain TCP schemes.
func WithWill ¶
func WithWill(w *WillOptions) Option
WithWill attaches a will message to be published by the broker if this client disconnects ungracefully.
func WithWriteBatch ¶
WithWriteBatch coalesces up to n queued packets into one writev syscall. Off by default (n=0 or 1). It saves syscalls when many goroutines publish concurrently and packets queue behind the writer; with one publisher or large payloads there is little to coalesce. Measure with your workload before enabling it.
func WithWriteOverflowPolicy ¶
func WithWriteOverflowPolicy(p WriteOverflowPolicy) Option
WithWriteOverflowPolicy selects the QoS 0 writer-queue overflow policy. Only meaningful for PublishFireAndForget — QoS 1/2 and PublishWaitForFlush always wait for either the broker or the writer to be ready.
func WithWriteQueueSize ¶
WithWriteQueueSize sets the internal MPSC write-channel buffer. Larger absorbs publish bursts; costs memory and queue latency.
func WithoutKeepAlive ¶
func WithoutKeepAlive() Option
WithoutKeepAlive disables MQTT keepalive entirely (no PINGREQ goroutine). Rarely correct in production — without PINGREQ the broker cannot detect a half-open connection from this side.
type PacketType ¶ added in v0.11.0
type PacketType = wire.PacketType
PacketType is an MQTT control packet type.
type PoolRoutingPolicy ¶
type PoolRoutingPolicy uint8
PoolRoutingPolicy selects how the publisher pool picks the starting member per Client.Publish. Unhealthy starting members fall through to the next member regardless of policy.
const ( // PoolRoutingRoundRobin (default) cycles through members; same // topic may arrive at the broker out of order. PoolRoutingRoundRobin PoolRoutingPolicy = iota // PoolRoutingHashByTopic picks the starting member by FNV-1a of // opts.Topic, preserving per-topic ordering while the pool is // healthy. Empty Topic collapses to member 0. PoolRoutingHashByTopic )
func (PoolRoutingPolicy) String ¶
func (p PoolRoutingPolicy) String() string
String returns a stable debug name for the policy.
type Properties ¶ added in v0.11.0
type Properties struct {
// contains filtered or unexported fields
}
Properties are the properties of a received PUBLISH (§3.3.2.3). Their memory follows the Message's: owned unless the subscription is zero-copy.
func (Properties) ContentType ¶ added in v0.11.0
func (p Properties) ContentType() string
ContentType returns the Content Type, or "".
func (Properties) CorrelationData ¶ added in v0.11.0
func (p Properties) CorrelationData() []byte
CorrelationData returns the Correlation Data, or nil.
func (Properties) MessageExpiryInterval ¶ added in v0.11.0
func (p Properties) MessageExpiryInterval() (uint32, bool)
MessageExpiryInterval returns the lifetime in seconds the broker left the message.
func (Properties) PayloadFormatIndicator ¶ added in v0.11.0
func (p Properties) PayloadFormatIndicator() (byte, bool)
PayloadFormatIndicator returns the Payload Format Indicator: 1 for UTF-8 text, 0 for bytes.
func (Properties) ResponseTopic ¶ added in v0.11.0
func (p Properties) ResponseTopic() string
ResponseTopic returns the Response Topic, or "".
func (Properties) SubscriptionIdentifiers ¶ added in v0.11.0
func (p Properties) SubscriptionIdentifiers() iter.Seq[uint32]
SubscriptionIdentifiers yields the identifiers of the subscriptions the broker matched (§3.3.4).
func (Properties) UserProperties ¶ added in v0.11.0
func (p Properties) UserProperties() iter.Seq2[string, string]
UserProperties yields every User Property in order.
func (Properties) UserProperty ¶ added in v0.11.0
func (p Properties) UserProperty(key string) (string, bool)
UserProperty returns the first value of the User Property named key.
type ProtocolError ¶ added in v0.11.0
type ProtocolError struct {
// Reason is the DISCONNECT reason code sent: 0x81 Malformed Packet,
// 0x82 Protocol Error, or a more specific code such as 0x93 Receive
// Maximum exceeded.
Reason wire.ReasonCode
Packet wire.PacketType
Detail string
}
ProtocolError is a broker packet that violates MQTT v5. The client sends DISCONNECT with Reason, closes the connection and reconnects.
func (*ProtocolError) Error ¶ added in v0.11.0
func (e *ProtocolError) Error() string
type PublishMode ¶
type PublishMode uint8
PublishMode controls QoS 0 Client.Publish write-completion semantics. QoS 1/2 always wait for the broker's ack.
const ( // PublishFireAndForget (default): Publish returns once the // message is queued for the writer goroutine. Transport errors // are not surfaced. PublishFireAndForget PublishMode = iota // PublishWaitForFlush makes Publish block until the packet is // written to the connection and surfaces transport errors. On a TCP // or Unix connection with nothing queued for the writer goroutine the // caller writes the packet itself: the header and the caller's // payload go out in one writev, and the payload is not copied. ctx // bounds the call either way. A write it interrupts is finished by // the writer goroutine, which copies what is left, so the packet may // still reach the broker after Publish returned ctx's error; one // interrupted before its first byte is not sent. Useful as a // connection-health probe. PublishWaitForFlush )
func (PublishMode) String ¶
func (m PublishMode) String() string
String returns a stable name for the mode.
type PublishOptions ¶ added in v0.11.0
type PublishOptions struct {
Topic string
Payload []byte
QoS byte // 0, 1 or 2
Retain bool
// PayloadFormatIndicator 1 declares the payload UTF-8 text, 0 bytes.
PayloadFormatIndicator *byte
// MessageExpiryInterval is the message's lifetime in seconds; 0 is
// valid (deliver now or never).
MessageExpiryInterval *uint32
ContentType string
ResponseTopic string
CorrelationData []byte
// TopicAlias sends an alias the broker already knows instead of the
// topic (QoS 0 only); see also WithOutboundTopicAliases.
TopicAlias uint16
UserProperties []UserProperty
}
PublishOptions is a message to publish. Topic is required unless a QoS 0 message uses TopicAlias; optional properties are sent only when set (a nil pointer or empty string or slice is absent).
func DecodePublish ¶ added in v0.11.0
func DecodePublish(frame []byte) (PublishOptions, uint16, error)
DecodePublish is the inverse of EncodePublish: it returns the options of the PUBLISH packet in frame and its packet identifier. The options own their memory.
type PublisherQueue ¶
type PublisherQueue interface {
// Enqueue stores e, assigning it the next Seq, which it returns.
// When the queue already holds limit.Max entries it refuses with
// ErrQueueFull or, under DropOldest, removes the oldest entries above
// limit.Keep and returns them; the check, the removal and the insert
// are one atomic step. It returns ErrQueueFull when there is nothing
// it may remove.
Enqueue(ctx context.Context, e QueueEntry, limit QueueLimit) (seq uint64, evicted []QueueEntry, err error)
// Peek returns up to n entries with Seq above after, oldest first.
Peek(ctx context.Context, after uint64, n int) ([]QueueEntry, error)
// Ack removes the entry with seq. Acking a missing entry is not an
// error.
Ack(ctx context.Context, seq uint64) error
// Len returns the number of entries, in constant time.
Len(ctx context.Context) (int, error)
// Close releases the queue's resources. Later calls return
// ErrQueueClosed.
Close() error
}
PublisherQueue stores the messages a QueuePublisher has accepted until the broker has them. Implementations must be safe for concurrent use and must not share memory with their callers: Enqueue copies the entry, and the entries they return belong to the caller. github.com/ashtonian/mqttv5/queue/queuetest checks an implementation against this contract.
type Queue ¶
type Queue[T any] struct { // contains filtered or unexported fields }
Queue is an unbounded MPSC FIFO of T. Enqueue is non-blocking; Dequeue blocks until an item arrives, the queue is closed, or ctx cancels. After Close, queued items are still returned by Dequeue; once drained, Dequeue returns (zero, false).
func NewQueue ¶
NewQueue returns a ready-to-use Queue.
Example ¶
ExampleNewQueue shows the lock-protected unbounded MPSC queue returned by mqttv5.Client.SubscribeQueue. It is exported so callers can build their own internal pipelines on top of the same type.
package main
import (
"context"
"fmt"
"github.com/ashtonian/mqttv5"
)
func main() {
q := mqttv5.NewQueue[string]()
go func() {
defer q.Close()
q.Enqueue("one")
q.Enqueue("two")
}()
ctx := context.Background()
for {
v, ok := q.Dequeue(ctx)
if !ok {
break
}
fmt.Println(v)
}
}
Output: one two
func (*Queue[T]) Close ¶
func (q *Queue[T]) Close()
Close signals waiting Dequeue calls to drain. Items already queued remain available.
func (*Queue[T]) Dequeue ¶
Dequeue blocks until an item is available, the queue is closed, or ctx cancels. ok is false when no item could be retrieved.
func (*Queue[T]) TryDequeue ¶
TryDequeue removes and returns the head without blocking.
type QueueEntry ¶
type QueueEntry struct {
// Seq is assigned by the queue at Enqueue. It increases strictly
// within a queue and is never reused.
Seq uint64
// ID identifies the message for good. [QueuePublisher] stores it with
// the session record of the message's exchange, so after a restart
// it continues that exchange instead of starting another, and sends
// it as the [QueueIDProperty] user property when
// [WithQueueIdempotencyKey] is set.
ID string
// Publish is the message, QoS 1 or 2. PacketID and Dup are unused;
// the Message Expiry Interval is carried by ExpiresAt.
Publish PublishOptions
EnqueuedAt time.Time
// ExpiresAt is when the message may no longer be sent: the earlier of
// its Message Expiry Interval and [WithQueueTTL], counted from
// EnqueuedAt. Zero means never.
ExpiresAt time.Time
}
QueueEntry is one message in a PublisherQueue.
type QueueLimit ¶ added in v0.11.0
type QueueLimit struct {
// Max is the most entries the queue may hold; 0 means no bound.
Max int
// Policy says what Enqueue does when the queue holds Max entries:
// [DropNewest] refuses the new entry with [ErrQueueFull];
// [DropOldest] removes the oldest entries with Seq above Keep to make
// room.
Policy DropPolicy
// Keep is the highest Seq DropOldest must not remove: the entries up
// to it are being published.
Keep uint64
}
QueueLimit bounds a PublisherQueue for one Enqueue.
type QueueOption ¶
type QueueOption func(*queueConfig)
QueueOption customises a QueuePublisher.
func WithDeadLetter ¶
func WithDeadLetter(fn func(QueueEntry, error)) QueueOption
WithDeadLetter receives every message removed without the broker accepting it, with the reason: a refusal WithQueueClassifier calls permanent, ErrMessageExpired, or ErrQueueFull for an eviction. It is called from internal goroutines, possibly concurrently, before the message leaves the queue; it must not block.
func WithQueueClassifier ¶ added in v0.11.0
func WithQueueClassifier(fn func(err error) bool) QueueOption
WithQueueClassifier decides which failed publishes are dead-lettered rather than retried: fn reports whether err is permanent. The default, PermanentPublishError, treats the broker's refusals for the message itself as permanent and everything else as passing.
func WithQueueDropPolicy ¶
func WithQueueDropPolicy(p DropPolicy) QueueOption
WithQueueDropPolicy decides what Publish does when the queue is at WithQueueMaxSize: DropNewest (default) returns ErrQueueFull; DropOldest removes the oldest messages not yet being published, passes them to the dead-letter callback with ErrQueueFull, and stores the new one.
func WithQueueIdempotencyKey ¶ added in v0.11.0
func WithQueueIdempotencyKey() QueueOption
WithQueueIdempotencyKey adds each message's QueueEntry.ID as the QueueIDProperty user property, so consumers can recognise a message published again after a session loss.
func WithQueueMaxSize ¶
func WithQueueMaxSize(n int) QueueOption
WithQueueMaxSize bounds the queue at n entries; at the bound WithQueueDropPolicy decides. 0 (default) means unbounded.
func WithQueueRetryBackoff ¶ added in v0.11.0
func WithQueueRetryBackoff(b Backoff) QueueOption
WithQueueRetryBackoff spaces the attempts of a message whose publish failed for a reason that may pass. Default DefaultQueueRetryBackoff.
func WithQueueTTL ¶
func WithQueueTTL(d time.Duration) QueueOption
WithQueueTTL limits how long a message may wait: it expires d after Publish (or earlier, at its own Message Expiry Interval). An expired message goes to the dead-letter callback with ErrMessageExpired; one that is sent carries the time it has left, in whole seconds rounded up, as its Message Expiry Interval.
func WithQueueWindow ¶ added in v0.11.0
func WithQueueWindow(n int) QueueOption
WithQueueWindow sets how many messages are published at once. Default DefaultQueueWindow; the broker's Receive Maximum still applies on top.
type QueuePublisher ¶
type QueuePublisher struct {
// contains filtered or unexported fields
}
QueuePublisher accepts QoS 1/2 messages into a PublisherQueue and publishes them in the background, many at a time.
QueuePublisher.Publish returns once the message is stored. A drain goroutine keeps up to WithQueueWindow messages in flight, in queue order, while the client is connected, and removes each from the queue when the broker has accepted it. A refusal that cannot pass (see WithQueueClassifier) and a message that expired go to the WithDeadLetter callback and are removed; other failures are retried with WithQueueRetryBackoff, after the messages behind them if need be, so one bad message never holds up the rest.
Each message has one MQTT exchange at a time. Its QueueEntry.ID is stored with the exchange in the client's session (WithStore), and the message is removed from the queue before the session record, so with a persistent queue and store a restarted process continues an interrupted exchange instead of publishing the message again: QoS 2 stays exactly-once across a crash, and QoS 1 is at most resent with DUP=1. When removing a finished message from the queue fails, the session keeps the exchange's record and the removal is retried with WithQueueRetryBackoff, in this process or after a restart, without publishing the message again. A message is published again as new only when the broker lost the session (see SessionLossPolicy).
The QueuePublisher publishes on the client's own connection, never through WithPublisherPool.
func NewQueuePublisher ¶
func NewQueuePublisher(client *Client, queue PublisherQueue, opts ...QueueOption) (*QueuePublisher, error)
NewQueuePublisher starts a QueuePublisher draining queue through client. Entries already in the queue are published in order once the client is connected; nothing is removed by construction.
Example ¶
ExampleNewQueuePublisher queues publishes and drains them to the broker from a background goroutine, many at a time. Publish returns once the queue has stored the entry, before the broker round-trip.
To keep queued and in-flight messages across a process restart, swap mqttv5.NewMemoryPublisherQueue for the queue/file submodule and give the client a store/file session store with mqttv5.WithStore.
package main
import (
"context"
"fmt"
"time"
"github.com/ashtonian/mqttv5"
)
func main() {
cli, _ := mqttv5.New(mqttv5.WithBroker("mqtt://localhost:1883"))
ctx := context.Background()
_ = cli.Connect(ctx)
defer cli.Disconnect(ctx)
pub, err := mqttv5.NewQueuePublisher(cli, mqttv5.NewMemoryPublisherQueue(),
mqttv5.WithQueueWindow(32),
mqttv5.WithQueueTTL(24*time.Hour),
mqttv5.WithDeadLetter(func(e mqttv5.QueueEntry, err error) {
fmt.Println("dead letter", e.Publish.Topic, err)
}),
)
if err != nil {
panic(err)
}
defer pub.Close(context.Background())
_ = pub.Publish(ctx, mqttv5.PublishOptions{
Topic: "events/durable",
Payload: []byte("at-least-once"),
QoS: 1,
})
}
Output:
func (*QueuePublisher) Close ¶
func (p *QueuePublisher) Close(ctx context.Context) error
Close stops publishing, waits until ctx for the exchanges in flight to finish, and closes the queue. Messages whose exchange is still running stay in the queue and in the session; the next QueuePublisher on them continues those exchanges.
func (*QueuePublisher) Publish ¶
func (p *QueuePublisher) Publish(ctx context.Context, opts PublishOptions) error
Publish stores a copy of opts in the queue and returns; the drain publishes it. The message must be valid to send (QoS 1 or 2, a topic name, no Topic Alias): an invalid one is refused here rather than dead-lettered later.
func (*QueuePublisher) PublishValue ¶ added in v0.12.0
func (p *QueuePublisher) PublishValue(ctx context.Context, opts PublishOptions, v any) error
PublishValue encodes v with the client's Encoding and queues it as QueuePublisher.Publish does. An encoding failure is returned here, never dead-lettered.
type ReasonCode ¶ added in v0.11.0
type ReasonCode = wire.ReasonCode
ReasonCode is an MQTT v5 reason code (§2.4). Codes of 0x80 and above are errors; see IsError and String.
type ReasonCodeError ¶ added in v0.11.0
type ReasonCodeError struct {
Packet wire.PacketType
Code wire.ReasonCode
ReasonString string
UserProperties []wire.UserProperty
ServerReference string
}
ReasonCodeError is a broker's refusal: a reason code of 0x80 or above on CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK or DISCONNECT, with the optional diagnostics the broker attached.
errors.Is matches it against ErrConnectRefused (for CONNACK) and against ErrNotAuthorized, ErrQuotaExceeded, ErrTopicNameInvalid, ErrPayloadFormatInvalid and ErrPacketTooLarge by code. Use errors.As to read the code itself.
func (*ReasonCodeError) Error ¶ added in v0.11.0
func (e *ReasonCodeError) Error() string
func (*ReasonCodeError) Is ¶ added in v0.11.0
func (e *ReasonCodeError) Is(target error) bool
Is reports whether e corresponds to target.
type RejectedByDisconnectError ¶ added in v0.11.0
type RejectedByDisconnectError struct {
Packet PacketType // SUBSCRIBE or UNSUBSCRIBE
Attempts int
Disconnect DisconnectInfo // the last of them
}
RejectedByDisconnectError reports a SUBSCRIBE or UNSUBSCRIBE the client gave up on: each of the Attempts times it was sent, the broker ended the connection with a DISCONNECT blaming a packet it received instead of answering. Some brokers do this for limits they do not announce, such as mosquitto's 200 topic levels. A Subscribe that fails this way has closed its subscription.
func (*RejectedByDisconnectError) Error ¶ added in v0.11.0
func (e *RejectedByDisconnectError) Error() string
type ServerFinalVerifier ¶ added in v0.10.0
ServerFinalVerifier is an optional interface an Authenticator may also implement to verify the server's concluding AuthenticationData — the CONNACK on the initial handshake, or the AUTH 0x00 Success that ends a re-authentication (§4.12). For mechanisms with mutual authentication (e.g. SCRAM, where the server proves knowledge of the credential via a server signature) this closes the loop; a non-nil error aborts the connect, or fails Reauthenticate and tears the connection down.
The serverData is whatever AuthenticationData the broker attached to the terminal packet (possibly empty). Authenticators whose mechanism has no server proof (e.g. a bearer token) simply do not implement this interface, and the terminal data is ignored. VerifyServerFinal is called on the same goroutine as the corresponding Continue.
type ServerRedirect ¶ added in v0.11.0
type ServerRedirect struct {
Packet PacketType // CONNACK or DISCONNECT
Reason ReasonCode
Reference string // as the broker sent it
}
ServerRedirect is a broker's request that the client use another server (§4.11): a CONNACK or DISCONNECT with reason 0x9C Use another server or 0x9D Server moved, carrying a Server Reference.
func (ServerRedirect) Permanent ¶ added in v0.11.0
func (r ServerRedirect) Permanent() bool
Permanent reports a move (0x9D Server moved) rather than a temporary redirection (0x9C Use another server).
type SessionLossPolicy ¶ added in v0.11.0
type SessionLossPolicy uint8
SessionLossPolicy decides what happens to unacknowledged QoS 1/2 publishes when a connection starts without the old session: the broker answered CONNACK with Session Present = 0 (restart without persistence, session expiry, failover to another broker in WithBrokers) or the client connected with CleanStart=1.
const ( // SessionLossRepublish sends them again as new messages (DUP=0) in // their original order; their Publish calls keep waiting for the new // acknowledgement. Delivery stays at-least-once, and a message the // old broker had already forwarded is delivered twice — including // QoS 2, whose exactly-once guarantee cannot survive a session loss. // Messages whose Message Expiry Interval ran out complete with // [ErrMessageExpired]; the rest are sent with the remaining interval. SessionLossRepublish SessionLossPolicy = SessionLossPolicy(inflight.Republish) // SessionLossFail completes them with [ErrSessionLost] and leaves // any retry to the caller. SessionLossFail SessionLossPolicy = SessionLossPolicy(inflight.Fail) )
type Stats ¶
type Stats struct {
Connects uint64 // successful CONNECT/CONNACK handshakes
ConnectFailures uint64 // CONNECT attempts that failed (any cause)
Disconnects uint64 // connections lost, for any reason but Disconnect
ServerDisconnects uint64 // broker-sent DISCONNECT packets
PingTimeouts uint64 // PINGRESP timeouts that forced a reconnect
PublishesSent uint64 // Publish calls that returned nil (QoS 1/2 once acknowledged), and QueuePublisher messages the broker accepted
PublishesAcked uint64 // PUBACK and PUBCOMP packets received, refusals included
PublishesInflight uint64 // QoS 1/2 outbound entries awaiting ack
SendQuota uint64 // QoS 1/2 PUBLISHes the broker's Receive Maximum still allows in flight
PublishesReplayed uint64 // QoS 1/2 replays issued after reconnect
InboundPublishes uint64 // PUBLISHes received from the broker
InboundDropped uint64 // inbound PUBLISHes dropped due to full subscriber buffer
InboundUnrouted uint64 // inbound PUBLISHes no subscription matched (acked and dropped)
DecodeFailures uint64 // typed-subscription payloads the Encoding rejected (acked, not delivered)
SubscribesSent uint64 // SUBSCRIBE packets emitted
UnsubscribesSent uint64 // UNSUBSCRIBE packets emitted
SubscriptionsActive uint64 // count of currently registered subscriptions
PoolFallbacks uint64 // publishes no pool member could send, handed to the main connection
PoolConnected uint64 // publisher pool members with a live connection
AcksIgnored uint64 // PUBACK/PUBREC/PUBCOMP for an unknown or mismatched packet identifier
StoreErrors uint64 // failed session Store operations (see WithStore)
ProtocolErrors uint64 // broker protocol violations that closed the connection
}
Stats is the snapshot of Client counters returned by Client.Stats.
Two flavours of field:
- Monotonic counters (Connects, ConnectFailures, Disconnects, ServerDisconnects, PingTimeouts, PublishesSent, PublishesAcked, PublishesReplayed, InboundPublishes, InboundDropped, InboundUnrouted, DecodeFailures, SubscribesSent, UnsubscribesSent, PoolFallbacks, AcksIgnored, StoreErrors, ProtocolErrors). They start at zero in New and are never reset: they span every Connect and Disconnect of the Client. Sampled atomically — no locking on the read side.
- Point-in-time gauges (PublishesInflight, SendQuota, SubscriptionsActive, PoolConnected) read live state under a mutex (the session state and the subscriptions map respectively). The locks are cheap but a scraper hitting Stats() at very high frequency does contend with Subscribe / Unsubscribe and the ack path. Default to sampling at most a few times per second.
With WithPublisherPool every field covers the pool members' connections as well as the main one: publishes made through the pool count as the Client's, and a pool member's reconnects count with the main connection's.
The zero value is returned when WithStats was not set on the Config — when stats are disabled the hot path skips every atomic increment, so leave WithStats off when you have no scraper.
type StoreError ¶ added in v0.11.0
type StoreError = inflight.StoreError
StoreError reports a failed write to the WithStore store: Op names the write and Err is the store's error. errors.Is matches both ErrStoreFailed and Err.
type SubscribeError ¶ added in v0.11.0
type SubscribeError struct {
Results []SubscribeResult
}
SubscribeError reports filters the broker refused. Results holds the outcome of every filter of the SUBSCRIBE. When some filters were granted the subscription is active for those; when none were, its channel or queue is closed.
func (*SubscribeError) Error ¶ added in v0.11.0
func (e *SubscribeError) Error() string
func (*SubscribeError) Is ¶ added in v0.11.0
func (e *SubscribeError) Is(target error) bool
Is matches the sentinel of any refusal's reason code (see ReasonCodeError).
type SubscribeOption ¶
type SubscribeOption func(*subscribeConfig)
SubscribeOption configures client-side delivery for one Subscribe call. Protocol flags (QoS, NoLocal, etc.) live on TopicFilter itself — these options control what happens after delivery.
func SubAutoAck ¶
func SubAutoAck() SubscribeOption
SubAutoAck makes Client.Subscribe / Client.SubscribeQueue ack each delivery on the read goroutine before handing it to the consumer; Message.Ack is then a no-op.
Auto-ack gives up at-least-once processing: a consumer that crashes between delivery and processing has nothing to replay, because the broker already considers the message delivered. It also gives up the broker's flow control, so a full buffer or queue drops QoS 1 and 2 messages as it drops QoS 0. Use it for QoS 0 or observational consumers; keep manual ack for ledger-style workloads.
Ignored by Client.SubscribeCallback (already auto-acks).
func SubBuffer ¶
func SubBuffer(n int) SubscribeOption
SubBuffer sets Client.Subscribe's channel buffer size: how many QoS 0 messages the channel holds before it drops one. QoS 1 and 2 messages have room beyond it (see Client.Subscribe). Default DefaultSubscribeBuffer. Ignored by Client.SubscribeQueue and Client.SubscribeCallback.
func SubDropPolicy ¶
func SubDropPolicy(p DropPolicy) SubscribeOption
SubDropPolicy chooses behaviour when the channel buffer or queue cap fills.
- Subscribe (chan): only DropNewest is supported. Passing DropOldest here causes Subscribe to return ErrChanDropOldestUnsupported — peek-and-pop on a consumer-owned channel would race the receiver. Use SubscribeQueue for DropOldest semantics.
- SubscribeQueue: both policies are honored. DropOldest keeps the newest messages up to the cap whatever their QoS, so it may drop QoS 1 and 2 messages too.
- SubscribeCallback: ignored (callback never drops; slow handler stalls the connection instead).
Default is the client-level WithDropPolicy (DropNewest).
func SubEncoding ¶ added in v0.12.0
func SubEncoding(e Encoding) SubscribeOption
SubEncoding decodes one typed subscription with e instead of the client's Encoding. Ignored by untyped subscriptions.
func SubMaxQueueSize ¶
func SubMaxQueueSize(n int) SubscribeOption
SubMaxQueueSize caps Client.SubscribeQueue's length for this subscription. QoS 1 and 2 messages have room beyond the cap (see Client.SubscribeQueue). UnboundedQueue removes the cap; 0 keeps the client default from WithMaxSubscribeQueueSize (DefaultMaxSubscribeQueueSize).
func SubOnDecodeError ¶ added in v0.12.0
func SubOnDecodeError(fn func(*Message, error)) SubscribeOption
SubOnDecodeError is called with each message of a typed subscription whose payload the Encoding rejects, and the error. Such a message is not delivered: it is acked, so the broker does not send it again, and counted in Stats.DecodeFailures. fn runs on the read goroutine before the ack, so it must not block, nor call Client.Disconnect.
func SubOnDrop ¶
func SubOnDrop(fn func(*Message)) SubscribeOption
SubOnDrop fires when a message is dropped by Client.Subscribe (buffer full) or Client.SubscribeQueue (cap reached). It runs on the read goroutine before the dropped message is acknowledged, so every field is readable; it must not block, nor call Client.Disconnect. On a SubZeroCopy subscription the fields must not be retained past return.
func SubZeroCopy ¶ added in v0.11.0
func SubZeroCopy() SubscribeOption
SubZeroCopy delivers Messages whose Topic, Payload and Properties alias the pooled network frame instead of an owned copy, saving one copy of the packet per message. The fields are valid only until the Message is acked by every subscription it reached (for Client.SubscribeCallback, until the callback returns); use Message.CloneTopic / Message.ClonePayload to keep them longer. QoS 0 messages must also be acked so the frame returns to the pool. A PUBLISH that also matches a subscription without SubZeroCopy is delivered as an owned copy to all of them. Ignored together with SubAutoAck, which acks before delivery and therefore needs an owned copy.
type SubscribeResult ¶ added in v0.11.0
type SubscribeResult struct {
Filter TopicFilter
// Reason is the SUBACK reason code: 0x00–0x02 grant that QoS,
// 0x80 and above refuse the filter.
Reason wire.ReasonCode
}
SubscribeResult is the broker's answer for one filter of a SUBSCRIBE.
func (SubscribeResult) Granted ¶ added in v0.11.0
func (r SubscribeResult) Granted() bool
Granted reports whether the broker accepted the filter.
func (SubscribeResult) QoS ¶ added in v0.11.0
func (r SubscribeResult) QoS() byte
QoS is the maximum QoS the broker granted; meaningful when Granted.
type SubscriptionToken ¶
type SubscriptionToken struct {
// contains filtered or unexported fields
}
SubscriptionToken identifies a subscription for Client.Unsubscribe and reports what the broker decided.
func (SubscriptionToken) Err ¶ added in v0.11.0
func (t SubscriptionToken) Err() error
Err reports why the subscription ended other than by Unsubscribe or Disconnect: the broker refused every filter, initially or when the client re-subscribed after a session loss. Nil while it is active.
func (SubscriptionToken) Results ¶ added in v0.11.0
func (t SubscriptionToken) Results() []SubscribeResult
Results returns the broker's answer per filter from the subscription's SUBACK, or nil before it arrived.
type TopicFilter ¶
type TopicFilter struct {
Topic string
QoS byte // maximum QoS: 0, 1 or 2
NoLocal bool
RetainAsPublished bool
RetainHandling byte // 0 send retained, 1 only for a new subscription, 2 never
}
TopicFilter is one entry of a SUBSCRIBE: a topic filter and its subscription options (§3.8.3.1). Only Topic is required.
{Topic: "events/#"} // QoS 0
{Topic: "events/#", QoS: 1} // at-least-once
{Topic: "events/#", QoS: 1, NoLocal: true} // don't echo own publishes back
type TypedMessage ¶
TypedMessage is a delivered message with its payload decoded. Value and Topic are safe to retain past Ack, including on SubZeroCopy subscriptions (where Topic is cloned off the frame); the embedded *Message follows the usual ownership rules for Payload and Properties.
type UnsubscribeError ¶ added in v0.11.0
type UnsubscribeError struct {
Results []UnsubscribeResult
}
UnsubscribeError reports topic filters the broker refused to unsubscribe. The local subscription is closed regardless; the broker may keep delivering messages for them, which the client then drops.
func (*UnsubscribeError) Error ¶ added in v0.11.0
func (e *UnsubscribeError) Error() string
type UnsubscribeResult ¶ added in v0.11.0
type UnsubscribeResult struct {
Topic string
// Reason is the UNSUBACK reason code: 0x00 success, 0x11 there was no
// such subscription (not an error), 0x80 and above refused.
Reason wire.ReasonCode
}
UnsubscribeResult is the broker's answer for one topic filter of an UNSUBSCRIBE.
type UserProperty ¶ added in v0.11.0
type UserProperty = wire.UserProperty
UserProperty is one User Property name/value pair. A packet may carry the same name more than once.
type WillOptions ¶ added in v0.11.0
type WillOptions struct {
Topic string
Payload []byte
QoS byte
Retain bool
// DelayInterval delays the Will by this many seconds after the
// connection ends; nil or 0 publishes it at once.
DelayInterval *uint32
PayloadFormatIndicator *byte
MessageExpiryInterval *uint32
ContentType string
ResponseTopic string
CorrelationData []byte
UserProperties []UserProperty
}
WillOptions is the Will Message the broker publishes for the client when its connection ends without a DISCONNECT (§3.1.3.2).
type WriteOverflowPolicy ¶
type WriteOverflowPolicy uint8
WriteOverflowPolicy controls what QoS 0 Client.Publish does when the internal writer queue (sized by WithWriteQueueSize) is full. QoS 1/2 are unaffected — they always block on the broker ack.
const ( // WriteBlock (default) blocks Publish until the writer queue has // room, the call's ctx fires, or the connection goes down. The // caller surfaces overload as latency. WriteBlock WriteOverflowPolicy = iota // WriteDropNewest returns [ErrWriteQueueFull] immediately when // the queue has no room. Use for high-rate fire-and-forget // telemetry where the producer goroutine must not block. WriteDropNewest )
func (WriteOverflowPolicy) String ¶
func (p WriteOverflowPolicy) String() string
String returns a stable name for the policy.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
codec
|
|
|
json
module
|
|
|
msgpack
module
|
|
|
internal
|
|
|
clock
Package clock abstracts time so timing-dependent code (keep-alive, backoff, queue draining) can be tested without sleeping.
|
Package clock abstracts time so timing-dependent code (keep-alive, backoff, queue draining) can be tested without sleeping. |
|
inflight
Package inflight is the client's MQTT v5 session state machine: packet identifier ownership, outbound QoS 1/2 flows, inbound QoS 1/2 flows and the acknowledgements owed to the broker.
|
Package inflight is the client's MQTT v5 session state machine: packet identifier ownership, outbound QoS 1/2 flows, inbound QoS 1/2 flows and the acknowledgements owed to the broker. |
|
soak
Package soak sizes the randomized tests.
|
Package soak sizes the randomized tests. |
|
testbroker
Package testbroker is a scripted MQTT v5 peer for tests.
|
Package testbroker is a scripted MQTT v5 peer for tests. |
|
trie
Package trie implements an MQTT v5 topic filter matcher used by the client to dispatch inbound PUBLISH packets to subscribed handlers.
|
Package trie implements an MQTT v5 topic filter matcher used by the client to dispatch inbound PUBLISH packets to subscribed handlers. |
|
filedb
module
|
|
|
queue
|
|
|
queuetest
Package queuetest checks a mqttv5.PublisherQueue implementation against the interface's contract.
|
Package queuetest checks a mqttv5.PublisherQueue implementation against the interface's contract. |
|
file
module
|
|
|
Package session defines how an MQTT v5 client persists its session state: the Store contract, the [Record]s it holds, and MemoryStore, an in-memory reference implementation.
|
Package session defines how an MQTT v5 client persists its session state: the Store contract, the [Record]s it holds, and MemoryStore, an in-memory reference implementation. |
|
storetest
Package storetest checks that a session.Store implementation honours the contract the client relies on.
|
Package storetest checks that a session.Store implementation honours the contract the client relies on. |
|
store
|
|
|
file
module
|
|
|
Package transport abstracts the network layer underneath the MQTT v5 client.
|
Package transport abstracts the network layer underneath the MQTT v5 client. |
|
ws
module
|
|
|
Package wire implements the MQTT v5 wire-format codec.
|
Package wire implements the MQTT v5 wire-format codec. |