KubeMQ
ConnectorsRabbitMQ (AMQP 0-9-1)

RabbitMQ (AMQP 0-9-1)

Point a RabbitMQ app at KubeMQ by changing only the connection string — AMQP 0-9-1 over KubeMQ Queues, where every queue is a Queue channel.

Point a RabbitMQ app at KubeMQ by changing only the connection string. The RabbitMQ (AMQP 0-9-1) connector is a built-in, wire-protocol bridge inside kubemq-server that speaks the RabbitMQ wire dialect natively — any standard AMQP 0-9-1 client (amqp091-go, pika, amqp-client, amqplib, RabbitMQ.Client, bunny, lapin) talks to KubeMQ's Queues with no KubeMQ SDK, no library swap, and no code rewrite.

What is the RabbitMQ connector

AMQP 0-9-1 is the wire protocol that RabbitMQ popularized: a client opens a connection, multiplexes channels over it, declares queues and exchanges, and publishes messages routed by exchange type and routing key. The KubeMQ connector accepts every one of these operations from a stock client and bridges them onto KubeMQ — it is a gateway, not a client library, so your application only needs its existing AMQP 0-9-1 client.

Mental model — everything is a Queue. Every AMQP queue maps to exactly one KubeMQ Queue channel named amqp.{vhost}.{queue}. Exchanges and bindings are virtual, connector-side routing metadata resolved at publish time — not data stores. AMQP only ever touches the KubeMQ Queue primitive, so the connector's "patterns" mirror AMQP routing concepts (work queues, pub/sub, routing, topics, RPC), not KubeMQ's five messaging patterns.

Key capabilities:

  • Drop-in connection-string migration — keep your RabbitMQ client and code; change only the broker host in the URL.
  • Everything is a Queue — every AMQP queue is a durable KubeMQ Queue channel amqp.{vhost}.{queue}; exchanges (default, direct, fanout, topic, headers) route to those queues virtually at publish time.
  • Native RPC — request/reply uses RabbitMQ's amq.rabbitmq.reply-to (direct reply-to); there is no gRPC responder.
  • Cross-protocol interop — a message published over AMQP to amqp.default.orders is consumable by a gRPC or REST KubeMQ client on the same channel, and vice-versa.

How it works

An AMQP client publishes to an exchange with a routing key. The connector resolves the exchange routing (default / direct / fanout / topic / headers) to a set of target queues at publish time, then writes each message to that queue's KubeMQ Queue channel through the message broker. A consumer on the same queue — over AMQP or any other KubeMQ transport — receives it.

A publish to queue orders on vhost / resolves to the KubeMQ Queue channel amqp.default.orders; any consumer on that channel — AMQP or gRPC/REST — receives the message.

Ports & protocol surface

PortTransportProtocolNotes
5672Plain TCP (SASL PLAIN)AMQP 0-9-1 (RabbitMQ wire dialect)Default plain listener. Shared with the AMQP 1.0 connector via the internal amqpmux.
5671TLS / AMQPS over TCPAMQP 0-9-1Binds only when the server-global Security block is configured. Shared TLS listener with AMQP 1.0.

A single amqpmux listener accepts every connection on 5672/5671, reads the 8-byte AMQP protocol header, and routes it to the matching dialect engine — so AMQP 0-9-1 and AMQP 1.0 coexist on the same ports. SASL is PLAIN only. The AMQP vhost / maps to the connector's configured DefaultVhost segment (literal "default"); see Architecture for the dispatch detail.

Publish to a queue

The example below declares a queue, publishes one text/plain message to it through the default exchange (routing key = queue name), and consumes it back. Queue hello on vhost / lands on the KubeMQ Queue channel amqp.default.hello. Every client reads the broker endpoint from KUBEMQ_AMQP_URL (default amqp://guest:guest@localhost:5672/).

Which to use

This is the same round trip as the Getting started tutorial, shown here inline for reference. For a step-by-step walkthrough — enabling the connector, running a local broker, and verifying each step — use Getting started instead.

package main

import (
	"context"
	"log"
	"os"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"
)

func amqpURL() string {
	if v := os.Getenv("KUBEMQ_AMQP_URL"); v != "" {
		return v
	}
	return "amqp://guest:guest@localhost:5672/"
}

func main() {
	conn, err := amqp.Dial(amqpURL())
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	defer func() { _ = conn.Close() }()

	ch, err := conn.Channel()
	if err != nil {
		log.Fatalf("channel: %v", err)
	}
	defer func() { _ = ch.Close() }()

	// Declare "hello" → KubeMQ Queue channel amqp.default.hello.
	q, err := ch.QueueDeclare("hello", false, false, false, false, nil)
	if err != nil {
		log.Fatalf("declare queue: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	// Publish on the default exchange — routing key = queue name.
	if err := ch.PublishWithContext(ctx, "", q.Name, false, false, amqp.Publishing{
		ContentType: "text/plain",
		Body:        []byte("Hello World!"),
	}); err != nil {
		log.Fatalf("publish: %v", err)
	}
	log.Printf(" [x] Sent %q", "Hello World!")

	// Consume with auto-ack and print the exact body.
	msgs, err := ch.Consume(q.Name, "", true, false, false, false, nil)
	if err != nil {
		log.Fatalf("consume: %v", err)
	}
	select {
	case d := <-msgs:
		log.Printf(" [x] Received %q", string(d.Body))
	case <-ctx.Done():
		log.Fatalf("timed out: %v", ctx.Err())
	}
}
import os

import pika

URL = os.environ.get("KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/")
QUEUE = "hello"  # → KubeMQ Queue channel amqp.default.hello


def main() -> None:
    connection = pika.BlockingConnection(pika.URLParameters(URL))
    channel = connection.channel()

    # Default (nameless) exchange: routing key == queue name.
    channel.queue_declare(queue=QUEUE, durable=False, exclusive=False, auto_delete=False)

    channel.basic_publish(
        exchange="",
        routing_key=QUEUE,
        body=b"Hello World!",
        properties=pika.BasicProperties(content_type="text/plain"),
    )
    print(" [x] Sent 'Hello World!'")

    method, _props, body = channel.basic_get(queue=QUEUE, auto_ack=True)
    if method is None:
        raise SystemExit("no message received from queue")
    print(f" [x] Received {body.decode()!r}")

    channel.close()
    connection.close()


if __name__ == "__main__":
    main()
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public final class Main {

    private static final String QUEUE = "hello"; // → amqp.default.hello

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(System.getenv().getOrDefault(
                "KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/"));

        try (Connection connection = factory.newConnection();
                Channel channel = connection.createChannel()) {

            // queue.declare: non-durable, non-exclusive, no auto-delete.
            channel.queueDeclare(QUEUE, false, false, false, null);

            // Publish via the default exchange ("") with routing key = queue name.
            AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                    .contentType("text/plain")
                    .build();
            channel.basicPublish("", QUEUE, props, "Hello World!".getBytes("UTF-8"));
            System.out.println("[x] Sent 'Hello World!'");

            BlockingQueue<String> received = new ArrayBlockingQueue<>(1);
            DeliverCallback onDeliver = (tag, delivery) ->
                    received.offer(new String(delivery.getBody(), "UTF-8"));
            channel.basicConsume(QUEUE, true, onDeliver, tag -> { });

            String got = received.poll(15, TimeUnit.SECONDS);
            if (got == null) {
                throw new IllegalStateException("timed out waiting for the message");
            }
            System.out.println("[x] Received '" + got + "'");
        }
    }
}
import amqp from "amqplib";

// amqplib reads the URL path as the vhost; the bare trailing "/" in the dev URL
// resolves to the default vhost (KubeMQ segment "default").
const URL = process.env.KUBEMQ_AMQP_URL ?? "amqp://guest:guest@localhost:5672/";
const QUEUE = "hello"; // → KubeMQ Queue channel amqp.default.hello

async function main() {
  const conn = await amqp.connect(URL);
  const ch = await conn.createChannel();

  await ch.assertQueue(QUEUE, { durable: false, autoDelete: false, exclusive: false });

  const received = new Promise((resolve) => {
    ch.consume(
      QUEUE,
      (msg) => {
        if (msg === null) return;
        console.log(`[x] Received: ${msg.content.toString()}`);
        resolve();
      },
      { noAck: true },
    );
  });

  // Default exchange ("") routes by queue name.
  ch.publish("", QUEUE, Buffer.from("Hello World!"), { contentType: "text/plain" });
  console.log("[x] Sent:     Hello World!");

  await received;
  await ch.close();
  await conn.close();
}

main().catch((err) => {
  console.error("publish failed:", err);
  process.exit(1);
});
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

const string queueName = "hello"; // → KubeMQ Queue channel amqp.default.hello

var factory = new ConnectionFactory
{
    Uri = new Uri(Environment.GetEnvironmentVariable("KUBEMQ_AMQP_URL")
                  ?? "amqp://guest:guest@localhost:5672/"),
};

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var ct = cts.Token;

await using var connection = await factory.CreateConnectionAsync(ct);
await using var channel = await connection.CreateChannelAsync(cancellationToken: ct);

// Non-durable, not exclusive, not auto-delete — a plain shared queue.
await channel.QueueDeclareAsync(queueName, durable: false, exclusive: false,
    autoDelete: false, arguments: null, cancellationToken: ct);

var received = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.ReceivedAsync += (_, ea) =>
{
    received.TrySetResult(Encoding.UTF8.GetString(ea.Body.Span));
    return Task.CompletedTask;
};
await channel.BasicConsumeAsync(queueName, autoAck: true, consumer: consumer, cancellationToken: ct);

// Publish to the default exchange with routing key = queue name.
var props = new BasicProperties { ContentType = "text/plain" };
await channel.BasicPublishAsync(exchange: "", routingKey: queueName, mandatory: false,
    basicProperties: props, body: Encoding.UTF8.GetBytes("Hello World!"), cancellationToken: ct);
Console.WriteLine("[x] Sent 'Hello World!'");

var message = await received.Task.WaitAsync(ct);
Console.WriteLine($"[x] Received '{message}'");
# frozen_string_literal: true
require "bunny"

URL = ENV.fetch("KUBEMQ_AMQP_URL", "amqp://guest:guest@localhost:5672/")
QUEUE = "hello" # → KubeMQ Queue channel amqp.default.hello

conn = Bunny.new(URL)
conn.start
ch = conn.create_channel

# Publisher confirms: wait for the broker to accept and route the publish before
# reading it back, so a fire-and-forget publish is never lost in flight.
ch.confirm_select

queue = ch.queue(QUEUE, durable: false, auto_delete: false, exclusive: false)

# Default ("") exchange routes by queue name.
ch.default_exchange.publish("Hello World!", routing_key: queue.name, content_type: "text/plain")
ch.wait_for_confirms
puts " [x] Sent 'Hello World!'"

body = nil
deadline = Time.now + 5
while body.nil? && Time.now < deadline
  _info, _props, body = queue.pop(manual_ack: false)
  sleep 0.2 if body.nil?
end
puts " [x] Received '#{body}'"

conn.close
use futures_lite::StreamExt;
use lapin::{
    options::{BasicConsumeOptions, BasicPublishOptions, QueueDeclareOptions},
    types::FieldTable,
    BasicProperties, Connection, ConnectionProperties,
};

const QUEUE: &str = "hello"; // → KubeMQ Queue channel amqp.default.hello

// lapin reads the URL path as the vhost and treats a bare trailing "/" as an
// empty vhost, which the connector rejects. The default "/" vhost must be
// percent-encoded as "%2f".
fn amqp_url() -> String {
    let url = std::env::var("KUBEMQ_AMQP_URL")
        .unwrap_or_else(|_| "amqp://guest:guest@localhost:5672/".into());
    let host = url.rsplit_once('@').map_or(url.as_str(), |(_, h)| h);
    if host.ends_with('/') && !host.ends_with("/%2f") {
        format!("{url}%2f")
    } else {
        url
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = Connection::connect(&amqp_url(), ConnectionProperties::default()).await?;
    let channel = conn.create_channel().await?;

    channel
        .queue_declare(QUEUE, QueueDeclareOptions::default(), FieldTable::default())
        .await?;

    // Publish to the default exchange, routing key = queue name.
    channel
        .basic_publish(
            "",
            QUEUE,
            BasicPublishOptions::default(),
            b"Hello World!",
            BasicProperties::default().with_content_type("text/plain".into()),
        )
        .await?
        .await?;
    println!("[x] Sent 'Hello World!'");

    let mut consumer = channel
        .basic_consume(
            QUEUE,
            "hello-consumer",
            BasicConsumeOptions { no_ack: true, ..Default::default() },
            FieldTable::default(),
        )
        .await?;

    if let Some(delivery) = consumer.next().await {
        let delivery = delivery?;
        println!("[x] Received '{}'", String::from_utf8_lossy(&delivery.data));
    }

    conn.close(0, "done").await?;
    Ok(())
}

Supported languages

The connector speaks standard AMQP 0-9-1, so any conformant RabbitMQ client works. The examples pin one native client per language — there is no KubeMQ SDK, no proto bindings, and no published package.

LanguageClient libraryNotes
Gogithub.com/rabbitmq/amqp091-goThe RabbitMQ team's Go client.
PythonpikaBlockingConnection with URLParameters.
Javacom.rabbitmq:amqp-clientThe official RabbitMQ Java client.
JavaScript / TypeScriptamqplibEncode the default / vhost as %2f in the URL.
C# / .NETRabbitMQ.ClientTask-based async API (v7+).
RubybunnyUse publisher confirms before consuming.
Rustlapinasync/await on Tokio; percent-encode the / vhost.

Next steps

Was this page helpful?

On this page