sondahub / NATS test server
NATS test server
A NATS server with JetStream, already busy: the hub’s live activity on subjects like fleet.telemetry and store.orders, a stream per API holding it, key-value buckets that follow the fleet and the exchange rates, and services that answer requests — orders that move, and every API call. Over WebSocket from the browser, or over TCP through a small bridge.
Free and open: no key, no account. Each WebSocket connection, and each run of the bridge, is a server of its own.
Connect
- WebSocket
- wss://api.sondahub.com/nats
- Over TCP
- through the bridge: node bridge.mjs nats [port], then nats://localhost:4322
- Sign-in
- none; or sonda/sondahub, viewer/viewer; or the token sondahub
- Server
- protocol 1, headers, JetStream; max_payload 1,048,576
NATS clients that speak WebSocket connect straight to the address above, each connection a server of its own. Clients on TCP go through the bridge, which listens on nats://localhost:4322 — off the usual 4222 — and carries every connection to the hub over one WebSocket: one bridge, one server, shared by every client that goes through it.
curl -O https://sondahub.com/industrial/bridge.mjs node bridge.mjs nats NATS on localhost:4322 → wss://api.sondahub.com/nats — nats://localhost:4322 every run of the bridge is a world of its own the hub is a NATS server with JetStream — the APIs’ activity on store.>, fleet.>, bank.>…, streams STORE, FLEET, BANK…, key-value buckets fleet and fx, services orders.create and rpc.> “orders-worker” — nats.js 2.29.1 connected from 127.0.0.1:52144 “orders-worker” subscribes to store.orders — the hub publishes there orders.create ← order 900001 for 42.5: paid in 2 s, shipped in 5, delivered in 9, on store.orders
In a NATS client
nats -s nats://localhost:4322 sub 'fleet.>'
nats -s nats://localhost:4322 req rpc.store.getProduct '{"id":1}'
nats -s nats://localhost:4322 req orders.create '{"customer_id":7,"total":42.5}'
nats -s nats://localhost:4322 stream info STORE
nats -s nats://localhost:4322 kv watch fleet
import { connect, StringCodec } from 'nats.ws'
const nc = await connect({ servers: 'wss://api.sondahub.com/nats' })
const sc = StringCodec()
for await (const m of nc.subscribe('bank.fx')) console.log(m.subject, sc.decode(m.data))
nc, _ := nats.Connect("nats://localhost:4322")
js, _ := jetstream.New(nc)
kv, _ := js.KeyValue(ctx, "fleet")
w, _ := kv.WatchAll(ctx)
for e := range w.Updates() {
if e != nil {
fmt.Println(e.Key(), string(e.Value()))
}
}
nc = await nats.connect("nats://localhost:4322")
js = nc.jetstream()
sub = await js.pull_subscribe("store.orders", durable="me", stream="STORE")
for m in await sub.fetch(10):
print(m.subject, m.data.decode())
await m.ack()
In LockFlare Sonda: a WebSocket request to wss://api.sondahub.com/nats, then the protocol as text messages, one line each — CONNECT {}, SUB fleet.telemetry 1, PUB greetings 5 followed by the payload. A line may leave off its CRLF; the server answers in text when you write text.
What is in it
Subjects the hub publishes on
Each message JSON, with the headers Content-Type: application/json and Sondahub-Key. Subscribe with wildcards: fleet.>, *.orders, >.
| Subject | What it carries |
|---|---|
| store.orders Sondahub-Key header: order id | Orders moving through their life — paid, shipped with a tracking number, delivered — one record per change. Produce your own order here and watch it move. |
| store.inventory Sondahub-Key header: SKU | Stock moving in the warehouses: picks and restocks. |
| fleet.telemetry Sondahub-Key header: device serial | Readings from the IoT fleet, about one a second, with metrics that walk instead of jumping. |
| fleet.devices Sondahub-Key header: device serial | Devices going degraded or offline and coming back. |
| fleet.alerts Sondahub-Key header: device serial | Alerts opened and cleared: thresholds, low battery, weak signal, offline. |
| bank.fx Sondahub-Key header: currency pair | Exchange rates with bid and ask, three a second. |
| bank.transactions Sondahub-Key header: account number | Card transactions on checking accounts, posted or pending. |
| social.posts Sondahub-Key header: username | New posts, with their hashtags. |
| social.likes Sondahub-Key header: post id | Likes landing on posts, with the running count. |
| social.comments Sondahub-Key header: post id | Comments on posts. |
| helpdesk.tickets Sondahub-Key header: ticket number | Tickets changing status and being assigned. |
| helpdesk.messages Sondahub-Key header: ticket number | Messages on tickets, from agents and customers. |
| flights.board Sondahub-Key header: flight number | The departures board: boarding, departed, in the air, landed — and delayed or cancelled. |
| flights.bookings Sondahub-Key header: flight number | Bookings and check-ins, with the seat and the seats left. |
| identity.logins Sondahub-Key header: user name | Sign-ins: the method, and why the ones that failed did. |
| identity.directory Sondahub-Key header: user name | People joining and leaving groups, deactivated, reactivated, retitled. |
Streams and key-value buckets
| Stream | What it holds |
|---|---|
| STORE store.> | The store API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| FLEET fleet.> | The fleet API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| BANK bank.> | The bank API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| SOCIAL social.> | The social API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| HELPDESK helpdesk.> | The helpdesk API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| FLIGHTS flights.> | The flights API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| IDENTITY identity.> | The identity API’s activity: 10 minutes of it, the last minute there from the start. Memory storage, limits retention. |
| KV_fleet key-value bucket fleet, history 5 | The latest reading of each device, keyed by serial: a key-value bucket the fleet writes to as telemetry comes in. Watch it. |
| KV_fx key-value bucket fx, history 5 | The latest rate of each currency pair, keyed by pair (EUR/USD), written three times a second. |
Services
| Subject | What it does |
|---|---|
| orders.create request | Send an order — JSON with customer_id and a total above 0, an id if you want to pick it — and the answer is {"accepted":true,…,"status":"pending"}; the order moves on store.orders: paid in 2 s, shipped in 5, delivered in 9, cancelled if it is over 10,000. Anything else is answered {"accepted":false,"error":…} with the Nats-Service-Error headers. |
| rpc.<api>.<call> request | rpc.store.getProduct with {"id":1}, rpc.bank.listTransactions with {"limit":5}…: every call JSON-RPC makes, the params as the payload, answered {"result":…} or {"error":{"code","message"}} — with Nats-Service-Error and Nats-Service-Error-Code (400, 404, 409…) on errors. |
| $SRV.PING, $SRV.INFO, $SRV.STATS NATS micro discovery | The hub’s services as a NATS micro service named sondahub, so nats micro ls and the micro libraries find them. |
Core NATS
Subjects with * and > wildcards, queue groups (each message to one member), headers (HPUB and HMSG), request and reply through inboxes, and no responders — a request nobody serves gets a 503 status at once when the client asked for it (headers and no_responders in CONNECT, as every modern client does). echo:false keeps a client’s own messages from it; verbose answers +OK; UNSUB with a count; the server PINGs every two minutes and closes a connection that does not answer twice. The -ERR texts are nats-server’s: 'Invalid Publish Subject', 'Maximum Payload Violation', 'Unknown Protocol Operation', 'Authorization Violation'.
JetStream
Streams: create, update, info, list, names (with a subject filter), purge (by filter, up to a sequence, or keeping some), delete, message get and delete, direct get (both forms). Limits, interest and work-queue retention; max messages, bytes, age, message size and messages per subject; discard old or new; deduplication by Nats-Msg-Id within the duplicate window; Nats-Expected-Stream, -Last-Sequence, -Last-Subject-Sequence and -Last-Msg-Id; rollups with Nats-Rollup; sealed, deny-delete and deny-purge.
Consumers: durable and ephemeral (gone after five seconds without interest, or their inactive threshold), push and pull, every deliver policy (all, last, new, by start sequence, by start time, last per subject), filter subjects, ack none, all or explicit, ack wait and redelivery, max deliver, max ack pending, headers only, idle heartbeats. Acks: +ACK, -NAK (with a delay), +WPI, +TERM, +NXT, and an answer to an ack sent with a reply subject. Pull requests with batch, max_bytes, expires, no_wait and idle_heartbeat, ending in 408 Request Timeout, 404 No Messages or 409 as nats-server ends them.
Key-value buckets are streams the clients make (KV_name on $KV.name.>), so the clients’ kv APIs work: put, get, history, delete and purge markers, watch, keys. Errors carry nats-server’s codes: 10059 stream not found, 10014 consumer not found, 10058, 10065, 10071, 10148…
At most 25 streams of yours, 100 consumers a stream and 32 MB stored, in memory, for as long as the server lives.
Sign-in
| Who | May |
|---|---|
| (nobody) no credentials | Everything. The server asks for none. |
| sonda password sondahub | Everything. |
| viewer password viewer | Subscribes anywhere; publishes only replies, acks and the JetStream calls that read (consumers included) — anything else is -ERR 'Permissions Violation for Publish to "…"'. |
| token sondahub auth_token | Everything. |
| Anyone else or a wrong password | -ERR 'Authorization Violation', and the connection closes. nkeys and JWTs are not offered. |
Questions
Do I need the bridge?
Not for a client that speaks NATS over WebSocket — nats.ws, or a ws:// or wss:// server URL in the Go, Java, .NET and Rust clients: connect to wss://api.sondahub.com/nats. A client on TCP needs it: node bridge.mjs nats opens nats://localhost:4322 and carries every connection over one WebSocket.
Why port 4322?
It is NATS’s 4222 plus 100, so a nats-server you already run keeps its port. Run the bridge again and it takes the next free port with a server of its own; give it one — node bridge.mjs nats 4222 — and it takes exactly that or stops.
Do my messages reach other people?
No. Over WebSocket each connection is a server of its own. Through the bridge, the clients of one bridge share one server — a publisher and a subscriber, a queue group of workers, a JetStream producer and consumer — and nobody else sees it. It all ends when the bridge’s connection does.
Is it nats-server?
No: it speaks the client protocol and JetStream’s API as nats-server 2.10.22 does, and says that version so clients turn on the features they look for. Clustering, leaf nodes, gateways, accounts, TLS, mirrors and sources, object stores and stream snapshots are not offered.