Use this file to discover all available pages before exploring further.
The Artemis WebSocket service broadcasts real-time messages from the server to connected browser clients. The main application publishes messages through an AMQP fan-out exchange (RabbitMQ), and the WebSocket service — a separate process — delivers those messages to the appropriate connected clients. This page covers the architecture, server-side publishing API, client connection model, and how to run the service locally.
Artemis main app │ │ amqp.add_item(routing_key=namespace, exchange="sutro") ▼ RabbitMQ (sutro fan-out exchange) │ │ every worker process binds an exclusive queue to the exchange ▼ artemis-service-websockets │ │ maps routing_key → socket namespace ▼ Browser clients (WebSocket connection)
The main application and the WebSocket service communicate only through AMQP. The WebSocket service never reads from the main application’s database.
Each worker process in the WebSocket service binds its own exclusive, auto-delete queue to the fan-out exchange, so every worker receives every message and can dispatch to any client it holds.
Clients do not connect to an open WebSocket endpoint. The main application generates a time-limited, signed URL that the browser uses to establish the connection.
r2/r2/lib/websockets.py
def make_url(namespace, max_age): """Return a signed URL for the client to use for websockets.""" signer = MessageSigner(g.secrets["websocket"]) signature = signer.make_signature( namespace, max_age=datetime.timedelta(seconds=max_age) ) query_string = urllib.urlencode({"m": signature}) return urlparse.urlunparse( ("wss", g.websocket_host, namespace, None, query_string, None) )
Parameters:
Parameter
Type
Description
namespace
str
The path the client will subscribe to (e.g. /live/t3_abc123).
max_age
int
Seconds the signed URL remains valid.
The returned URL has the form:
wss://<websocket_host><namespace>?m=<signature>
The browser passes this URL directly to the WebSocket constructor. The WebSocket service validates the m query parameter against the shared secret before accepting the connection.
The signing secret (g.secrets["websocket"]) must match the secret/websockets/authorization_key value in the WebSocket service’s secrets file. A mismatch will cause all connections to be rejected.
The path portion of the WebSocket URL is the namespace. It serves two purposes:
Authorization scope — The signature binds to this exact path, so a token for /live/t3_abc123 cannot be used to connect to /live/t3_xyz789.
Message routing — The WebSocket service maps the AMQP routing key to the socket namespace. A call to send_broadcast(namespace="/live/t3_abc123", ...) reaches only clients connected to that path.
The WebSocket service is configured through an ini file. The key sections are:
example.ini
[app:main]factory = artemis_service_websockets.app:make_app; AMQP broker connectionamqp.endpoint = rabbit.local:5672amqp.vhost = /amqp.username = guestamqp.password = guest; fan-out exchange the main app writes toamqp.exchange.broadcast = sutro; topic exchange for connect/disconnect status eventsamqp.exchange.status = artemis_exchange; set true to publish connect/disconnect events to the status exchangeamqp.send_status_messages = false; seconds between unsolicited PING frames (Firefox requires activity < 55s)web.ping_interval = 45; base64-encoded token for admin-only service endpointsweb.admin_auth = aHVudGVyMg==; connections per second to shed when the service is quiescingweb.conn_shed_rate = 5secrets.path = example_secrets.json[server:main]factory = baseplate.server.wsgihandler = artemis_service_websockets.socketserver:WebSocketHandler
In production, replace current with a randomly generated 32-byte value encoded as base64. The vault_token field is used when the secrets store is backed by HashiCorp Vault.
When amqp.send_status_messages = true, the service publishes events to the topic exchange (amqp.exchange.status) whenever a client connects or disconnects. The routing key format is:
websocket.<event>.<namespace_path>
Backend consumers (queue workers) can bind to this exchange to react to client presence changes — for example, to trigger a cache warm-up when a user connects to a live thread.