django: a context bundle over the socket

The socket carried rpc but not ctx, so a socket transport could not fetch a
context and every app needed an HTTP connection beside it. Dispatches through
the same execute_context the HTTP view calls; that view's origin-side cache is
a CDN concern with nothing in front of a socket, so it is not on this path.

Both backends now answer subscribe/unsubscribe/message/rpc/ctx on one envelope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 19:28:12 -04:00
parent b5a95e8dcc
commit 398c90fc8b

View File

@@ -12,6 +12,7 @@ Protocol:
# RPC calls (server functions) # RPC calls (server functions)
{"action": "rpc", "id": "request-id", "fn": "function_name", "args": {...}} {"action": "rpc", "id": "request-id", "fn": "function_name", "args": {...}}
{"action": "ctx", "id": "request-id", "context": "name", "params": {...}}
Server sends: Server sends:
# Channel messages # Channel messages
@@ -167,6 +168,8 @@ class DjangoReactConsumer(AsyncJsonWebsocketConsumer):
await self._handle_message(content) await self._handle_message(content)
elif action == "rpc": elif action == "rpc":
await self._handle_rpc(content) await self._handle_rpc(content)
elif action == "ctx":
await self._handle_ctx(content)
else: else:
await self.send_json( await self.send_json(
{ {
@@ -503,6 +506,61 @@ class DjangoReactConsumer(AsyncJsonWebsocketConsumer):
await self.send_json({"id": request_id, "ok": True, "data": data}) await self.send_json({"id": request_id, "ok": True, "data": data})
async def _handle_ctx(self, content: dict):
"""
Handle a context-bundle fetch.
Protocol:
Request: {"action": "ctx", "id": "request-id", "context": "name", "params": {...}}
Response: {"id": "request-id", "ok": true, "data": {fn_name: result, ...}}
or: {"id": "request-id", "ok": false, "error": {...}}
Dispatches through the same execute_context the HTTP view calls. That view's
origin-side cache is a CDN concern with nothing in front of a socket, so it is not
part of this path.
"""
from mizan.client.executor import execute_context, FunctionError
request_id = content.get("id")
context_name = content.get("context")
if not request_id:
await self.send_json({"error": "ctx request missing 'id' field"})
return
if not context_name:
await self.send_json(
{
"id": request_id,
"ok": False,
"error": {"code": "BAD_REQUEST", "message": "Missing 'context' field"},
}
)
return
ws_request = WebSocketRequest(
self.scope, channel_name=getattr(self, "channel_name", None)
)
result = await sync_to_async(execute_context, thread_sensitive=True)(
ws_request, context_name, content.get("params") or {}
)
if isinstance(result, FunctionError):
await self.send_json(
{
"id": request_id,
"ok": False,
"error": {
"code": result.code.value,
"message": result.message,
**({"details": result.details} if result.details else {}),
},
}
)
return
await self.send_json({"id": request_id, "ok": True, "data": result.data})
async def channel_message(self, event: dict): async def channel_message(self, event: dict):
""" """
Handle messages broadcast to a group. Handle messages broadcast to a group.