Named Sockets
Caspian's WebSocket layer exposes named Python
@socket() handlers
to PulsePoint through pp.socket(...). Every
live channel shares one secure framework endpoint.
"websocket": true in
caspian.config.json, then run the Caspian project
update workflow so src/lib/websocket/sockets.py
and the application bootstrap are generated together.
Bidirectional
Browser and server send JSON values for the life of the page.
Policy-aware
Auth, roles, origin checks, capacity, rate, size, and idle limits are centralized.
Broadcast-ready
Sender handles and pools support rooms, presence, chat, and collaboration.
Define a named socket
from src.lib.websocket.sockets import Socket, socket @socket(require_auth=True) async def project_feed(project_id: str, socket: Socket): while (message := await socket.recv()) is not None: sent = await socket.send({ "projectId": project_id, "message": message, }) if not sent: break
- The handler is asynchronous and declares one parameter named
socket. - Client arguments are filtered against the declared signature.
recv()returns the next JSON value orNoneafter disconnect.send()returns false when the browser has gone away.
Connect from PulsePoint
<section> <button onclick="sendUpdate()">Send update</button> <script> const feed = pp.ref(null); const [events, setEvents] = pp.state([]); pp.effect(() => { feed.current = pp.socket("project_feed", { project_id: "alpha" }, { onMessage: (value) => setEvents((current) => [...current, value]), onError: (error) => console.error(error.message), }); return () => feed.current.close(); }, []); function sendUpdate() { feed.current.send({ type: "refresh" }); } </script> </section>
Keep the socket handle in pp.ref(...), state only
for values that render, and close the channel from the owning effect's cleanup.
PulsePoint already knows the shared /__pulsepoint/ws endpoint.
Broadcast with pools
from src.lib.websocket.sockets import Socket, SocketPool, socket room = SocketPool() @socket(allowed_roles=["member", "admin"]) async def team_room(name: str, socket: Socket): sender = socket.sender() room.add(sender) try: while (value := await socket.recv()) is not None: await room.broadcast({"from": name, "value": value}) finally: room.discard(sender)
Choose the transport
| Application need | Caspian transport |
|---|---|
| Forms, CRUD, uploads, button actions | @rpc() + pp.rpc() |
| One request with progressive server output | RPC streaming / SSE |
| Chat, presence, collaboration, multiplayer state | @socket() + pp.socket() |
| Binary or specialized non-JSON protocol | App-owned raw WebSocket endpoint |
The named-socket endpoint validates origins before upgrade and enforces connection, message-size, rate, and idle limits. Authentication and roles delegate to Caspian Auth, and WebSocket session access remains read-only.