Reference server and browser client for the STOMP-X protocol defined in stomp-x-spec.md.
This repository is intentionally small and explicit: it is meant to be easy to inspect while still demonstrating required STOMP-X behavior.
GitHub Pages hosts the spec at:
https://advanced-chat.github.io/stomp-x-spec/
Deployment is automated via .github/workflows/pages.yml on pushes to main.
stomp-x-spec.md: STOMP-X protocol specification used by this projectserver/main.go: Go WebSocket STOMP/STOMP-X reference serverserver/main_test.go: unit tests for core protocol behaviorclient/src/app.ts: browser TypeScript STOMP-X clientclient/index.html: test UI served by the Go process
- Go 1.22+
- Node.js 20+ and npm
- Build the browser client:
npm --prefix client install
npm --prefix client run build- Start the server:
go run ./server- Open the UI:
http://localhost:8080/index.html
- Native STOMP WebSocket endpoint:
ws://localhost:8080/stomp-x/websocket - SockJS base path (exposed):
http://localhost:8080/stomp-x - SockJS nested path (exposed):
http://localhost:8080/stomp-x/*
Notes:
/stomp-x/websocketis fully implemented./stomp-xand/stomp-x/*are currently placeholders that return501 Not Implemented(path exposure only).
- Accepts
CONNECTorSTOMP. - Requires
accept-versioncontaining1.2. - Requires
StompX-UserandStompX-User-Agent. - Returns
CONNECTEDwith:version: 1.2session: <generated session id>heart-beat(echoed from request when provided; otherwise0,0)
CONNECT/STOMPSUBSCRIBEUNSUBSCRIBESENDACKNACKDISCONNECT
SENDis restricted to/application/....SUBSCRIBEis allowed for:/topic/.../user/queue/.../application/...(for relays)
Domain MESSAGE payloads use the STOMP-X envelope:
{
"type": "event.type",
"resource": {}
}- If a client supplies
receiptonSEND, server emitsRECEIPT. - Action-result
MESSAGEframes includereceipt-idmatching the request.
- Relay destinations are treated as one-shot interactions:
- exact
.relayforms - common
.relay.*forms (page/cursor variants)
- exact
- After a relay response is sent, the server removes the subscription.
- Header override precedence is implemented for page/cursor parameters:
- SUBSCRIBE headers override destination-encoded parameters for shared keys.
Server supports:
ack:auto(default on missing/invalid mode)ack:clientack:client-individual
For non-auto subscriptions, each MESSAGE includes both:
message-idack
ACK/NACK handling:
- Client must ACK/NACK with
idmatching MESSAGEackheader. client-individual: only referenced message is acknowledged.client: cumulative ack for referenced + prior unacked messages on same subscription.
Application errors are delivered to /user/queue/errors with JSON payload:
{
"error": "ErrorCode",
"message": "Human readable message",
"timestamp": "2026-02-10T00:00:00.000000000Z",
"coolOffDuration": "PT5S"
}Additional error headers when available:
subscription-idfor SUBSCRIBE/relay-related failuresreceipt-idwhen request included areceipt
If no authenticated user context exists yet, server emits STOMP ERROR.
/application/echo.action/application/channels.create/application/channels/{channel_id}.messages.create
/topic/echo/topic/channels/topic/channels/{channel_id}.messages/user/queue/errors
/application/user.relay/application/user.read_file_access_grant.relay/application/user.write_file_access_grant.relay/application/channels.count.relay/application/channels/{channel_id}.relay/application/channels.relay.page.{page}.size.{size}.sort.{sort}/application/channels/{channel_id}.messages.count.relay/application/channels/{channel_id}.messages.relay.page.{page}.size.{size}.sort.{sort}/application/channels/{channel_id}.messages.relay.start.{start}.next.{next}.size.{size}.relation.{relation}
The browser client (client/src/app.ts):
- Sends STOMP 1.2 CONNECT headers including required STOMP-X user headers.
- Subscribes with
client-individualby default. - Sends
receiptonSEND,SUBSCRIBE,UNSUBSCRIBE, andDISCONNECT. - Automatically ACKs MESSAGE frames for non-
autosubscriptions. - Validates/logs event envelopes and structured error payloads.
Run server tests:
go test ./...Build/check client TypeScript:
npm --prefix client run buildStatus labels:
Implemented: behavior is present in this reference implementation.Partial: requirement is partially covered, intentionally limited, or only exposed as a placeholder.Not Implemented: out of scope for this sample.
| Requirement | Spec Section | Status | Notes |
|---|---|---|---|
STOMP 1.2 negotiation (accept-version:1.2) |
4.1 | Implemented | CONNECT/STOMP rejected unless 1.2 is advertised. |
Required CONNECT headers (StompX-User, StompX-User-Agent) |
5.1 | Implemented | Missing values return STOMP ERROR and close the connection. |
CONNECTED includes version and session |
5.4 | Implemented | CONNECTED includes version:1.2 and generated session id. |
/stomp-x/websocket endpoint exposed |
3.2 | Implemented | Native WebSocket endpoint is fully implemented. |
/stomp-x SockJS base exposed |
3.2 | Partial | Path is exposed, but SockJS transport semantics are not implemented (501 placeholder). |
Absolute destination namespaces (/application, /topic, /user/queue) |
6.1 | Implemented | Server enforces send/subscribe namespace usage. |
Error queue at /user/queue/errors |
6.2 / 11.1 | Implemented | Structured application errors are delivered as MESSAGE frames. |
Event envelope shape (type, resource) |
7.1 | Implemented | Domain MESSAGE payloads follow STOMP-X envelope. |
| Unknown envelope fields ignored by client | 7.2 | Implemented | Client parser accepts additional fields. |
Action receipt behavior (RECEIPT + receipt-id on outcomes) |
8.2 | Implemented | SEND with receipt gets RECEIPT and correlated outcome MESSAGE. |
| Relay lifecycle one-shot behavior | 9.2 | Implemented | Relay subscriptions are removed after response delivery. |
| Relay parameter precedence (headers override path) | 9.3 | Implemented | Page/cursor relay handlers prefer SUBSCRIBE header values. |
| Common relay path forms (page/cursor) | 9.4 | Implemented | Both page and cursor relay patterns are supported. |
ACK modes: auto, client, client-individual |
10.2 | Implemented | All required modes supported, invalid values default to auto. |
message-id and ack headers on manual-ack MESSAGE frames |
10.3 | Implemented | Applied to domain and error MESSAGE delivery paths. |
Error correlation headers (subscription-id, receipt-id) |
11.2 | Implemented | Added when source context is available. |
| STOMP ERROR fallback before user context exists | 11.3 | Implemented | Used for pre-auth/sessionless error conditions. |
API key carriage acceptance (api-key, Api-Key) |
3.3 | Partial | Endpoint accepts query/header pass-through, but no validation is enforced. |
Auth params Base64 JSON (StompX-Auth-Params) |
5.3 | Not Implemented | Optional auth extension is not interpreted in this sample. |
| Streams/binary upload endpoints | 12 | Not Implemented | HTTP stream upload profile is out of scope for this server. |
| TLS requirement in deployment | 13 | Not Implemented | Demo server runs plain HTTP/WS; TLS expected at deployment edge. |
- Full SockJS transport is not implemented; only required path exposure exists.
- No authentication backend or API-key validation is enforced in this sample.
- In-memory store only; no persistence.
- No TLS termination in this demo process.