HTTP Transports: Longpoll & SSE
Bondy provides two HTTP-based transports for WAMP sessions, enabling WAMP communication in environments where WebSocket connections are unavailable or impractical.
Overview
HTTP Transports
HTTP transports allow clients to establish WAMP sessions using standard HTTP requests instead of WebSocket connections. They provide the same WAMP semantics (RPC and Pub/Sub) through a request/response interface combined with either long-polling or Server-Sent Events for server-to-client messaging.
Both transports support cookie-based authentication, designed to work with OIDC authentication: after a user authenticates via OIDC, the browser sends the resulting ticket cookie with every transport request, so the web login carries over to the WAMP session without a separate authentication step.
When to Use HTTP Transports
| Transport | Best For |
|---|---|
| WebSocket | Full-duplex, low-latency WAMP communication (preferred when available) |
| Longpoll | Environments that block WebSocket (corporate proxies, restrictive firewalls) |
| SSE | Browser-based apps that need efficient server push with HTTP/2 multiplexing |
Long Polling
The Longpoll transport uses a request/response cycle to exchange WAMP messages. The client sends messages via POST and receives messages by issuing blocking POST requests that the server holds open until a message is available or a timeout occurs.
Protocol
The Longpoll transport uses the wamp.2.json sub-protocol. All WAMP messages are JSON-encoded.
Session Lifecycle
Client Bondy
│ │
│ POST /wamp/longpoll/open │
│ {"protocols":["wamp.2.json"]}│
│──────────────────────────────>│
│ 200 {"transport":"<id>"} │
│<──────────────────────────────│
│ │
│ POST /<id>/send │
│ [HELLO, ...] │
│──────────────────────────────>│
│ 202 Accepted │
│<──────────────────────────────│
│ │
│ POST /<id>/receive │
│ (blocks until message ready) │
│──────────────────────────────>│
│ 200 [WELCOME, ...] │
│<──────────────────────────────│
│ │
│ ... send/receive cycle ... │
│ │
│ POST /<id>/close │
│──────────────────────────────>│
│ 202 Accepted │
│<──────────────────────────────│Endpoints
| Route | Method | Description |
|---|---|---|
/wamp/longpoll/open | POST | Create a new transport session |
/wamp/longpoll/:transport_id/send | POST | Send a WAMP message |
/wamp/longpoll/:transport_id/receive | POST | Long-poll for a WAMP message |
/wamp/longpoll/:transport_id/close | POST | Terminate the transport session |
All endpoints also accept OPTIONS for CORS preflight.
Open
Creates a new transport session.
curl -X POST "https://api.example.com/wamp/longpoll/open" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <csrf_token>" \
-b "bondy_ticket_com.example.myrealm=<jwt>; bondy_csrf_com.example.myrealm=<csrf_token>" \
-d '{"protocols": ["wamp.2.json"]}'{
"protocol": "wamp.2.json",
"transport": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}If a bondy_ticket_<realm> cookie is present and valid, the ticket claims are stored in the transport session and used for cookie-based authentication.
Send
Sends a JSON-encoded WAMP message.
curl -X POST "https://api.example.com/wamp/longpoll/<transport_id>/send" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <csrf_token>" \
-b "bondy_ticket_com.example.myrealm=<jwt>; bondy_csrf_com.example.myrealm=<csrf_token>" \
-d '[1, "com.example.myrealm", {"authmethods": ["cookie"], "roles": {"caller": {}, "subscriber": {}}}]'Returns 202 Accepted on success, 404 if the transport is not found. Bondy refuses a body larger than 4 MiB with 413.
Receive
Blocks until a WAMP message is available or the timeout expires.
Returns 200 with the JSON-encoded WAMP message, or 204 No Content on timeout.
INFO
The listener's longpoll.poll_timeout sets how long Bondy holds a receive request, and defaults to 30 seconds. A client sends a new receive request immediately after each response, which keeps the long-polling loop running.
Close
Terminates the transport session and the associated WAMP session.
Returns 202 Accepted.
Server-Sent Events (SSE)
The SSE transport uses POST endpoints for client-to-server messages (same as Longpoll) and a persistent SSE connection for server-to-client messages. This provides more efficient server push compared to long-polling.
Protocol
The SSE transport uses the wamp.2.json.sse sub-protocol.
Session Lifecycle
Client Bondy
│ │
│ POST /wamp/sse/open │
│ {"protocols":["wamp.2.json.sse"]}
│──────────────────────────────>│
│ 200 {"transport":"<id>"} │
│<──────────────────────────────│
│ │
│ GET /<id>/receive │
│──────────────────────────────>│
│ 200 text/event-stream │
│<──────────── SSE stream ──────│
│ │
│ POST /<id>/send │
│ [HELLO, ...] │
│──────────────────────────────>│
│ 202 Accepted │
│<──────────────────────────────│
│ │
│ event: wamp │
│ data: [WELCOME, ...] │
│<──────────────────────────────│
│ │
│ ... send/receive cycle ... │
│ │
│ POST /<id>/close │
│──────────────────────────────>│
│ 202 Accepted │
│<──────────────────────────────│Endpoints
| Route | Method | Description |
|---|---|---|
/wamp/sse/open | POST | Create a new transport session |
/wamp/sse/:transport_id/send | POST | Send a WAMP message |
/wamp/sse/:transport_id/receive | GET | Open an SSE event stream |
/wamp/sse/:transport_id/close | POST | Terminate the transport session |
All endpoints also accept OPTIONS for CORS preflight.
Open
Identical to the Longpoll /open endpoint, but the protocol is wamp.2.json.sse:
curl -X POST "https://api.example.com/wamp/sse/open" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <csrf_token>" \
-b "bondy_ticket_com.example.myrealm=<jwt>; bondy_csrf_com.example.myrealm=<csrf_token>" \
-d '{"protocols": ["wamp.2.json.sse"]}'{
"protocol": "wamp.2.json.sse",
"transport": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Send
Identical to the Longpoll /send endpoint.
Receive (SSE Stream)
Opens a persistent SSE connection. Unlike Longpoll, this is a GET request that returns a text/event-stream response.
curl -N "https://api.example.com/wamp/sse/<transport_id>/receive" \
-H "Accept: text/event-stream" \
-b "bondy_ticket_com.example.myrealm=<jwt>"WAMP messages arrive as SSE events:
event: wamp
data: [2, 1, {}]
event: wamp
data: [36, 1234, 5678, {}, ["Hello, world!"]]Special events:
event: transport_errorwithdata: session_terminatedis sent when the transport session ends.- Keepalive comments (
: keepalive) are sent at the listener'ssse.ping.interval, 20 seconds by default, to prevent connection timeout.
Close
Identical to the Longpoll /close endpoint.
Cookie-Based Authentication
When a client opens a transport session (/open) and the request includes a valid bondy_ticket_<realm> cookie, Bondy uses cookie-based authentication to establish the WAMP session.
How It Works
During
/open, Bondy reads thebondy_ticket_<realm>cookie, verifies the JWT signature, and stores the ticket claims in the transport session.The client sends a WAMP
HELLOmessage withauthmethods: ["cookie"]:json[1, "com.example.myrealm", { "authmethods": ["cookie"], "roles": { "caller": {}, "subscriber": {} } }]Bondy detects the stored auth claims and authenticates the session using the
cookieauth method — no challenge is issued, and aWELCOMEmessage is returned immediately.On subsequent
/sendand/receiverequests, Bondy re-validates the ticket cookie to ensure the session remains authorized.
Important
The realm's authmethods configuration has to include both "oidcrp" and "cookie" for the OIDC-to-WAMP flow to work, and a source must allow cookie from the client's address:
{
"uri": "com.example.myrealm",
"authmethods": ["cryptosign", "wampcra", "ticket", "oidcrp", "cookie"]
}CSRF Protection
CSRF validation is enforced on the following endpoints:
| Endpoint | CSRF Required |
|---|---|
/open | Yes |
/send | Yes |
/receive | No |
/close | Yes |
The /receive endpoint is exempt because it is either a blocking read (Longpoll) or a GET SSE stream, neither of which can be exploited via CSRF.
To pass CSRF validation, include the X-CSRF-Token header with the value of the bondy_csrf_<realm> cookie:
const csrfToken = document.cookie
.split('; ')
.find(c => c.startsWith('bondy_csrf_com.example.myrealm='))
?.split('=')[1];
fetch('/wamp/longpoll/open', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
credentials: 'include',
body: JSON.stringify({ protocols: ['wamp.2.json'] })
});Error Responses
All transport endpoints return JSON error bodies:
| Status | Error | Description |
|---|---|---|
| 400 | no_supported_protocol | The protocols array in /open doesn't contain a supported protocol |
| 400 | invalid_json | The request body is not valid JSON |
| 400 | method_not_allowed | The request uses a method the endpoint does not accept |
| 400 | message_error | The body of a /send request is not a valid WAMP message |
| 401 | unauthorized | The ticket cookie is missing, invalid, or doesn't match the session |
| 403 | csrf_validation_failed | The X-CSRF-Token header is missing or doesn't match the CSRF cookie |
| 404 | transport_not_found | The transport_id doesn't correspond to an active session |
| 413 | payload_too_large | Long-poll only: the request body is larger than 4 MiB |
The two refusals of /open, no_supported_protocol and invalid_json, carry only the error key:
{
"error": "invalid_json"
}Every other error carries the standard error payload plus an error key that holds the short name above. A client can branch on the status or on the error key.
Transport Session Lifecycle
Each HTTP transport session is backed by a persistent server-side process that:
- Holds the WAMP protocol state (session, subscriptions, registrations)
- Buffers outbound WAMP messages until the client polls for them
- Times out after prolonged inactivity (default: 1 hour)
- Cleans up the WAMP session when the transport process terminates
Inactivity Timeout
If no client requests arrive within wamp.http_transport.idle_timeout, Bondy terminates the transport session and its WAMP session, and discards the queued messages of that transport. Each request resets the timeout. For SSE, an active SSE stream connection prevents the inactivity timeout from firing.
The outbound queue
A long-poll client is not connected at all between polls, and an SSE stream can drop and reconnect while the session survives. Messages for such a session therefore wait in a bounded queue per transport. The node-wide wamp.http_transport.queue.* keys bound it; WebSocket and RawSocket sessions never use it.
- When a new message would exceed
max_messagesormax_bytes, Bondy evicts the oldest entry of that transport to make room. Bondy applies no other eviction policy. - A message older than
message_ttlis not delivered: a poll skips it and the periodic sweep removes it. - The router's messages and the synchronous replies to the client's own messages, such as an inline
ERROR, share one queue, in the order they were produced.
When the router ends a session with ABORT or GOODBYE, Bondy queues the terminal message, hands it to a long-poll request that is waiting at that moment, and then closes the session. A long-poll client that is between polls at that moment does not receive the terminal message: its next poll answers 404 with transport_not_found. A poll that is waiting when the session dies gets the same answer.