WebSockets
After mastering Server-Sent Events for one-way communication, Batman realized he needed something more powerful. When Commissioner Gordon wanted to chat with him in real-time during crisis situations, Batman needed bidirectional communication.
"SSE is great for pushing updates to my dashboard," Batman thought, "but I need two-way communication for coordinating with my allies!"
To handle real-time bidirectional communication, Batman learned how to work with WebSockets using Robyn's modern decorator-based API. Under the hood, messages flow through Rust channels for maximum performance — no Python GIL overhead during message dispatch.
Request
from robyn import Robyn
app = Robyn(__file__)
@app.websocket("/web_socket")
async def handler(websocket):
while True:
msg = await websocket.receive_text()
await websocket.send_text(f"Echo: {msg}")
app.start()
Receiving Messages
The receive_text() method blocks until the next message arrives from the client. It is backed by a Rust tokio::mpsc channel, so the Python handler genuinely suspends without holding the GIL.
When the client disconnects, receive_text() raises WebSocketDisconnect. You can either catch it explicitly or let the internal wrapper handle it silently.
Receiving Messages
@app.websocket("/ws")
async def handler(websocket):
try:
while True:
msg = await websocket.receive_text()
await websocket.send_text(f"Got: {msg}")
except WebSocketDisconnect:
print(f"Client {websocket.id} disconnected")
Sending Messages
To send a message to the current client, use send_text() or send_json(). All send methods are async.
Sending Messages
@app.websocket("/ws")
async def handler(websocket):
while True:
msg = await websocket.receive_text()
await websocket.send_text(f"Echo: {msg}")
Broadcasting
To send a message to all connected clients on the same WebSocket endpoint, use the broadcast() method.
Broadcasting
@app.websocket("/chat")
async def handler(websocket):
while True:
msg = await websocket.receive_text()
# Send to all connected clients
await websocket.broadcast(f"User {websocket.id}: {msg}")
# Also send a confirmation to this client only
await websocket.send_text("Your message was sent")
Query Parameters
You can access query parameters from the WebSocket connection URL via websocket.query_params.
Query Params
@app.websocket("/ws")
async def handler(websocket):
name = websocket.query_params.get("name")
role = websocket.query_params.get("role")
if name == "gordon" and role == "commissioner":
await websocket.broadcast("Gordon authorized!")
while True:
msg = await websocket.receive_text()
await websocket.send_text(f"Hello {name}: {msg}")
Easy Access Query Parameters
Instead of manually calling websocket.query_params.get(...), you can declare typed query parameters directly in your handler, on_connect, and on_close signatures. Robyn will automatically resolve and coerce them — just like HTTP easy access parameters.
Parameters with defaults are optional. Parameters without defaults are required — if missing, the connection is rejected with an error message.
Easy Access Query Params
@app.websocket("/ws")
async def handler(websocket, room: str = "default", page: int = 1):
try:
while True:
msg = await websocket.receive_text()
await websocket.send_text(
f"room={room} page={page} msg={msg}"
)
except WebSocketDisconnect:
pass
Closing Connections
To programmatically close a WebSocket connection from the server side, use websocket.close(). This will:
- Close the WebSocket connection.
- Remove the client from the WebSocket registry.
- Cause any pending
receive_text()to raiseWebSocketDisconnect.
Close Connection
@app.websocket("/ws")
async def handler(websocket):
while True:
msg = await websocket.receive_text()
if msg == "quit":
await websocket.close()
break
await websocket.send_text(f"Got: {msg}")
Connect and Close Callbacks
You can attach optional on_connect and on_close callbacks to your WebSocket handler. These are decorators on the handler function itself.
on_connectis called when a new client connects. Its return value is sent to the client as the first message.on_closeis called when the connection closes. Its return value is sent to the client as the final message.
Both callbacks receive a websocket object with access to id and query_params. Both are optional.
Callbacks
@app.websocket("/chat")
async def chat(websocket):
while True:
msg = await websocket.receive_text()
await websocket.broadcast(msg)
@chat.on_connect
def on_connect(websocket):
return f"Welcome, {websocket.id}!"
@chat.on_close
def on_close(websocket):
return "Goodbye!"
WebSocket API Reference
The websocket object passed to handlers exposes the following methods and properties:
| Method / Property | Description |
|---|
| await websocket.receive_text() | Block until next message; raises WebSocketDisconnect on close |
| await websocket.receive_bytes() | Block until next binary message; raises WebSocketDisconnect on close |
| await websocket.receive_json() | Same as receive_text() but JSON-decoded |
| await websocket.send_text(data) | Send string to this client |
| await websocket.send_bytes(data) | Send binary data to this client |
| await websocket.send_json(data) | Send JSON to this client |
| await websocket.broadcast(data) | Send to all clients on this endpoint |
| await websocket.close() | Close the connection server-side |
| websocket.id | Connection UUID string |
| websocket.query_params | Query parameters from the connection URL |
What's next?
As the codebase grew, Batman wanted to onboard the justice league to help him manage the application.
Robyn told him about the different ways he could scale his application, and how to use views and subrouters to make his code more readable.
