WebSockets¶
Nori offers a native, object-oriented implementation for WebSockets, built on Starlette's asynchronous capabilities. Instead of dealing with loose functions and confusing callbacks, WebSockets in Nori are handled through dedicated classes (Handlers) that encapsulate the connection lifecycle.
WebSocket Handlers¶
Handlers must inherit from WebSocketHandler or its convenience variant JsonWebSocketHandler (located in core.ws). JsonWebSocketHandler inherits from WebSocketHandler, so both share the same lifecycle and configuration.
Creating a Basic Handler (JSON)¶
For most modern implementations (chats, notifications, real-time dashboards), you will want to communicate in JSON format.
Create a file in the modules directory, for example rootsystem/application/modules/chat_ws.py:
from core.ws import JsonWebSocketHandler
class ChatHandler(JsonWebSocketHandler):
async def on_connect(self, websocket):
"""Triggered when a client attempts to connect."""
print(f"New client connected. Sending welcome message.")
await websocket.send_json({"event": "welcome", "message": "Connected to the Nori server"})
async def on_receive_json(self, websocket, data: dict):
"""Triggered when the server receives a JSON message from the client."""
print(f"Message received: {data}")
# Echoing the message back to the client
await websocket.send_json({
"event": "echo",
"original_data": data
})
async def on_disconnect(self, websocket, close_code: int):
"""Triggered when the connection is lost or closed."""
print(f"Client disconnected with code: {close_code}")
The Handler Lifecycle¶
on_connect(websocket): Runs after the connection is accepted. Validate session cookies, verify tokens, or query the DB. To reject a connection, callawait websocket.close(code).on_receive(websocket, data)/on_receive_json(websocket, data): The heart of communication. InWebSocketHandler,datais a raw string. InJsonWebSocketHandler,datais already parsed as a Python dictionary (overrideon_receive_jsoninstead ofon_receive).on_disconnect(websocket, close_code): Ideal for clearing memory, removing the user from active rooms, or updating status in the database.
Idle Timeout¶
Both handlers enforce an idle timeout (default: 5 minutes). If no message is received within the timeout window, the connection is gracefully closed with code 1000. Override receive_timeout on your subclass to customize:
Authentication in WebSockets¶
Since SessionMiddleware populates websocket.session (WebSocket shares Starlette's HTTPConnection), you can check the session in on_connect to reject unauthenticated clients:
class ProtectedChatHandler(JsonWebSocketHandler):
async def on_connect(self, websocket):
user_id = websocket.session.get('user_id')
if not user_id:
await websocket.close(code=4001) # Custom close code
return
# Store user info for later use in on_receive
websocket.state.user_id = user_id
await websocket.send_json({"event": "welcome", "user_id": user_id})
async def on_receive(self, websocket, data: dict):
user_id = websocket.state.user_id
await websocket.send_json({"from": user_id, "message": data.get("message")})
For JWT-based authentication, read the token from a query parameter (/ws/chat?token=...) since browsers cannot set custom headers on WebSocket connections:
from core.auth.jwt import verify_token
async def on_connect(self, websocket):
token = websocket.query_params.get('token')
payload = verify_token(token) if token else None
if not payload:
await websocket.close(code=4001)
return
websocket.state.user_id = payload['user_id']
Routing WebSockets¶
Unlike HTTP routes (Route()), WebSockets are explicitly registered using the WebSocketRoute() class in your routes.py file.
# routes.py
from starlette.routing import Route, WebSocketRoute
from modules.chat_ws import ChatHandler
routes = [
# ... your HTTP routes ...
# Mounting the WebSocket
WebSocketRoute('/ws/chat', ChatHandler()),
]
Connections in the Frontend (Vanilla JS)¶
Connecting from the HTML/JavaScript browser is straightforward and instantiated via the browser standard WebSocket.
// Make sure to use wss:// if you are in production under HTTPS
const ws = new WebSocket('ws://localhost:8000/ws/chat');
ws.onopen = function(event) {
console.log("WebSocket Opened");
// Sending a JSON payload to the server
ws.send(JSON.stringify({ subject: "Hello Server!" }));
};
ws.onmessage = function(event) {
// Listening to JSON sends back from the Server
const response = JSON.parse(event.data);
console.log("Server says:", response);
};
ws.onclose = function(event) {
console.log("WebSocket Closed");
};
(Remember that if you configure your deployment on a VPS with Nginx as a reverse proxy, the Nginx location /ws block will require the directives proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "Upgrade"; to avoid truncating the channel).