Apache Kafka protocol plugin for the Bowire workbench. Browse topics, consume live traffic, produce test messages, and replay recorded Kafka streams through the Bowire mock server. Built on Confluent.Kafka.
kafka://broker.example:9092 # single broker
kafka://b1:9092,b2:9092,b3:9092 # bootstrap servers CSV
broker.example:9094 # bare host:port also accepted
kafka://broker:9092?schema-registry=http://sr:8081 # with Confluent Schema Registry
kafka://broker:9092?sr=http%3A%2F%2Fsr%3A8081 # short alias + URL-encoded
A short-lived IAdminClient queries broker metadata. Every non-internal topic surfaces as a service with two methods:
| Method | Kind | Description |
|---|---|---|
consume |
ServerStreaming | Subscribe with a throwaway group id (bowire-<hex>), yield one envelope per message. |
produce |
Unary | Publish one message; optional key and partition via metadata. |
A synthetic Cluster service surfaces broker / topic counts so the sidebar shows "what's there" even before you pick a topic. Internal topics (__consumer_offsets, _schemas, _confluent*) are hidden unless the workbench's internal-services toggle is on.
Each message arrives as:
{
"topic": "orders",
"partition": 0,
"offset": 1234,
"timestamp": 1745500800123,
"key": "order-42",
"keyBase64": "b3JkZXItNDI=",
"value": "{\"id\":42,\"total\":19.95}",
"valueBase64": "eyJpZCI6NDIsInRvdGFsIjoxOS45NX0=",
"encoding": "avro",
"bytes": 23
}key / value are the UTF-8 decode when the bytes map cleanly; null for binary payloads. keyBase64 / valueBase64 are always present so the workbench can hex-dump arbitrary payloads (Avro, Protobuf, MessagePack, …). The encoding field is always present (nullable) — it's set when the body was decoded through the schema registry, otherwise null.
When the URL carries ?schema-registry=… and the message body is framed in the Confluent wire format (0x00 magic byte + 4-byte big-endian schema id + payload), the plugin decodes it on the fly and the value field carries the JSON projection. Coverage at v0:
| Confluent schema type | Decoded? | encoding tag |
|---|---|---|
| Avro | Yes | "avro" |
| JSON Schema | Yes (raw text — same path as Avro) | "avro" (v0 — JSON Schema-specific tagging is a follow-up) |
| Protobuf | No (falls back to base64) | not set |
Plain UTF-8 / opaque binary stays in the original fallback path — the registry isn't consulted unless the framing prefix is present.
Two markers feed the Kafka security knobs from Bowire's environment auth helpers:
| Marker | Source | Effect |
|---|---|---|
__bowireMtls__ |
shared mtls auth helper across REST / gRPC / WebSocket / SignalR / Kafka |
SecurityProtocol = Ssl, SslCertificatePem / SslKeyPem / SslCaPem populated from PEM strings (no temp files), optional SslKeyPassword. allowSelfSigned flips EnableSslCertificateVerification = false. |
__bowireKafkaSasl__ |
Kafka-specific JSON { mechanism, username, password }. Mechanism strings are uppercase with dashes: "PLAIN", "SCRAM-SHA-256", "SCRAM-SHA-512", "OAUTHBEARER" (matches the Confluent canonical form). |
Maps to SaslMechanism.Plain / ScramSha256 / ScramSha512 / OAuthBearer. Combined with __bowireMtls__ → SecurityProtocol = SaslSsl; alone → SaslPlaintext. |
Both markers are stripped from the metadata dict before it's forwarded as Kafka message headers, so secrets never reach the wire.
The produce method publishes a single message taken from the first invocation payload:
| Metadata key | Purpose |
|---|---|
key |
Message key (UTF-8 string) |
partition |
Target partition number (optional — round-robins when absent) |
KafkaMockEmitter plugs into Bowire's mock server via IBowireMockEmitter. Recordings tagged protocol: "kafka" get re-published at the original cadence:
| Metadata key (first step) | Purpose | Default |
|---|---|---|
bootstrap (or bootstrap-servers) |
Broker CSV | localhost:9092 |
Per-step key / partition |
Same knobs as the live produce path | — |
Payload source: responseBinary (base64) is preferred so Avro / Protobuf round-trips byte-for-byte; text-only recordings fall back to UTF-8 encoding of body.
dotnet tool install -g Kuestenlogik.Bowire.Tool
bowire plugin install Kuestenlogik.Bowire.Protocol.Kafka
Then start the workbench and enter a Kafka broker URL in the sidebar.
A runnable end-to-end sample lives under samples/Kuestenlogik.Bowire.Protocol.Kafka.Sample — a single-node Kafka cluster (Docker Compose) plus an ASP.NET Core host that publishes harbour-domain port-call and berth-status events on two topics, so the live consume stream is never quiet.
cd samples/Kuestenlogik.Bowire.Protocol.Kafka.Sample
docker compose up -d
dotnet run --project .Then open http://localhost:5080/bowire, pick the Kafka tab, point it at kafka://localhost:9092, and stream harbor.port-calls → consume.
The unit-test suite is hermetic and runs anywhere dotnet test does:
dotnet test --filter "Category!=Docker"
Real-broker round-trips (KafkaRoundTripE2ETests, KafkaSchemaRegistryE2ETests) sit behind the Docker xUnit trait. They use Testcontainers to spin up Confluent Kafka 7.7 (KRaft mode, no Zookeeper) and Schema Registry 7.7 in a shared bridge network, then drive the plugin against the live processes:
docker info # confirm Docker Desktop / engine is reachable
dotnet test # all tests, Docker ones included
This plugin is the generic Kafka baseline: schema-registry-agnostic, serializer-agnostic, usable against any cluster. The Bowire.Protocol.Surgewave plugin layers Surgewave-specific conventions (schema-registry URL resolution, typed payload decoders, Surgewave topic-naming awareness) on top for users inside the Surgewave ecosystem. Run either alone or install both side by side — each registers under its own protocol id (kafka vs surgewave).