mirror of
https://github.com/hanzoai/amqp.git
synced 2026-10-30 14:57:07 +00:00
3.8 KiB
3.8 KiB
amqp
AMQP 0-9-1 over Hanzo PubSub (NATS JetStream). Read README.md first — the mapping table there IS the design.
Layout
wire/— the octet layer. Frames (frame.go) and the typed values a frame body carries (value.go: field tables, content properties). Knows the format, nothing about meaning.EncodeProps/DecodePropsserve both the content header and the bus, so a message's properties have one encoding.protocol/— the semantics.route.go— the whole AMQP↔JetStream map. Start here.topology.go— exchanges, queues, bindings in KV bucketamqp, mirrored per replica through a watch.broker.go— listener, stream, per-queue consumers,Connect/Accept/Serve/Ready/Shutdown.session.go— one connection: handshake, frame loop, faults.channel.go— one channel: every method, publish assembly, delivery.
cmd/amqp/— process lifecycle and nothing else.
Things that will bite you
Ready(), not a sleep.Serveblocks; everything that can fail has failed by the timeReadycloses. A caller that must fail closed waits on it against a deadline.ServeisConnect+ listen +Accept. A host that owns the port (cloud relays :5672 to a unix socket and starts the gateway on the first connection) callsConnect, which fails closed on the bus and binds nothing, then handsAcceptits own listener.- A queue IS its consumer.
queue.declarecreates the JetStream consumer, withDeliverNew. That is what makes a new queue start empty instead of replaying the stream's history. - Prefetch is enforced by a bucket in
channel.go, not by the pull batch.PullMaxMessagesis a fetch size, not a ceiling on what is outstanding. - Delivery tags must arrive in order or
basic.ack(multiple)acks the wrong set — hencechannel.deliver, held across the socket write. The state lock is released before that write, so a client that stops reading cannot stop its own acks from being processed. - A channel exception must not kill the connection. After sending
channel.closethe session ignores that channel untilclose-ok(session.closing); the peer had frames in flight. wire.Readerenforces frame-max. The size field is 32 bits: without it, seven octets from a stranger ask for a 4 GiB allocation. A content HEADER is the same shape of claim one level up, so a declared body size is refused against the bus's ownMaxPayloadbefore a byte of it is read — the gateway names no second number.- Exclusive and auto-delete name DIFFERENT events, and conflating them deletes
other people's queues. Exclusive belongs to the connection that declared it
(
Broker.claim, refused elsewhere with 405); auto-delete dies when its LAST CONSUMER goes (channel.reap, off the consumer count). Making both die with the connection made two connections co-owners of one exclusive queue, so either one's close took the other's. - Exclusivity is enforced within ONE gateway. A replica cannot see another replica's sockets, so two of them can each hold what the other thinks is exclusive. Say so; do not imply a guarantee the shape cannot make.
Tests
protocol/amqp_test.go drives github.com/rabbitmq/amqp091-go — the reference
client, unmodified — against a real broker over an embedded NATS. That is the
only test that means anything for a wire protocol; a hand-rolled client would
agree with a hand-rolled server about a shared misreading of the spec. Add to
it before you add anywhere else.
GOWORK=off go test ./... -race
Licensing
MIT OR Apache-2.0, at your option — per HIP-0137. Original Hanzo work: the
AMQP 0-9-1 framing and the JetStream translation were written here, not forked
from streadway/amqp or any other AMQP library, so there is no upstream
licence to inherit.