orchestrata_ultimate.gateway.protocol
Reference › orchestrata_ultimate › gateway › protocol
protocol holds MessageProcessor, which decodes inbound WebSocket frames and pushes outbound events. server is its importer; it is constructed with the rpc handler, the session connection manager, the socket map and an optional pubsub manager. See gateway.
process validates its arguments (TypeError or ValueError), counts the frame in metrics, and parses it into a WSMessage from models. Unparseable input gets an Invalid message reply. A HEARTBEAT frame updates the connection's heartbeat; only RPC_REQUEST frames go on to _dispatch_rpc, which calls RPCHandler.dispatch and replies with an RPC_RESPONSE carrying the result, or an ERROR message if RPCError is raised. A publish call is also fanned out to other clients through _broadcast_legacy.
broadcast is the outbound path: it asks the pub/sub manager for connections whose topic pattern and filters match, increments a per-topic sequence counter, and sends each a JSON-RPC 2.0 notification with no id whose params hold the sequence, a timestamp and the payload. It returns the number of clients reached and logs, without raising, any send that fails. push_to_client sends a notification to one connection.
At a glance
| Source | src/orchestrata_ultimate/gateway/protocol.py |
| Lines of code | 244 |
| Classes | 1 |
| Functions | 0 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.gateway.exceptions, orchestrata_ultimate.gateway.metrics, orchestrata_ultimate.gateway.models, orchestrata_ultimate.gateway.pubsub, orchestrata_ultimate.gateway.rpc, orchestrata_ultimate.gateway.session |
| Imported by | orchestrata_ultimate.gateway.server |
| Tests that import it | tests/gateway/handlers/test_config.py, tests/gateway/handlers/test_cxp.py, tests/gateway/test_gateway_handler.py, tests/gateway/test_gateway_metrics.py, tests/gateway/test_protocol.py, tests/gateway/test_protocol_behavioral.py, tests/gateway/test_protocol_dispatch.py, tests/gateway/test_protocol_ws_integration.py, tests/gateway/test_publisher_wiring.py, tests/gateway/test_rpc_behavioral.py, tests/gateway/test_subscription_filters.py, tests/generated_gateway/test_protocol_generated.py |
Class diagram
Dependency diagram
API
Classes
MessageProcessor
Line 27
Handles inbound messages after authentication.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(rpc: RPCHandler, connections: ConnectionManager, ws_map: dict[str, Any], pubsub: PubSubManager | None = None) -> None |
Wire processor to gateway internals. |
process |
async process(conn_id: str, raw: str) -> None |
Parse and route one inbound frame. |
_dispatch_rpc |
async _dispatch_rpc(conn_id: str, msg: WSMessage) -> None |
Dispatch an RPC request and send the response. |
broadcast |
async broadcast(topic: str, event_payload: dict[str, Any]) -> int |
Broadcast a JSON-RPC 2.0 notification to subscribers. |
push_to_client |
async push_to_client(connection_id: str, method: str, params: dict[str, Any]) -> bool |
Send a JSON-RPC notification to a specific client. |
_broadcast_legacy |
async _broadcast_legacy(sender_id: str, result: dict[str, Any]) -> None |
Push an event to matching subscribers (legacy format). |
_send_to |
async _send_to(conn_id: str, text: str) -> None |
Send text to a specific connection. |
Source
"""WebSocket message processing and broadcast logic.
Supports both internal WSMessage format and JSON-RPC 2.0
notification pushes with per-topic sequence numbers.
"""
import json
import logging
from collections import defaultdict
from datetime import UTC, datetime
from typing import Any
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.gateway.exceptions import RPCError
from orchestrata_ultimate.gateway.metrics import message_received, message_sent
from orchestrata_ultimate.gateway.models import (
MessageType,
WSMessage,
)
from orchestrata_ultimate.gateway.pubsub import PubSubManager
from orchestrata_ultimate.gateway.rpc import RPCHandler
from orchestrata_ultimate.gateway.session import ConnectionManager
logger = logging.getLogger(__name__)
class MessageProcessor:
"""Handles inbound messages after authentication.
Also provides JSON-RPC 2.0 notification broadcasting
for server-push event delivery to subscribers.
"""
def __init__(
self,
rpc: RPCHandler,
connections: ConnectionManager,
ws_map: dict[str, Any],
pubsub: PubSubManager | None = None,
) -> None:
"""Wire processor to gateway internals.
Args:
rpc: RPC method dispatcher.
connections: Connection lifecycle manager.
ws_map: Map of connection_id to websocket.
pubsub: Optional pub/sub manager for broadcasts.
"""
self._rpc = rpc
self._connections = connections
self._ws_map = ws_map
self._pubsub = pubsub
self._sequence_counters: dict[str, int] = defaultdict(int)
@traces(spec="014", story="US-011", reqs=["REQ-014-168"])
async def process(self, conn_id: str, raw: str) -> None:
"""Parse and route one inbound frame.
Args:
conn_id: Originating connection ID.
raw: Raw JSON string from websocket.
Raises:
TypeError: If conn_id or raw are not strings.
ValueError: If conn_id is empty.
"""
if not isinstance(conn_id, str):
raise TypeError("conn_id must be a str")
if not conn_id:
raise ValueError("conn_id must not be empty")
if not isinstance(raw, str):
raise TypeError("raw must be a str")
# REQ-014-144: one inbound frame received, valid or not.
message_received()
try:
msg = WSMessage.from_dict(json.loads(raw))
except (
json.JSONDecodeError,
KeyError,
ValueError,
):
await self._send_to(conn_id, "Invalid message")
return
if msg.msg_type == MessageType.HEARTBEAT:
await self._connections.update_heartbeat(conn_id)
return
if msg.msg_type != MessageType.RPC_REQUEST:
return
await self._dispatch_rpc(conn_id, msg)
async def _dispatch_rpc(self, conn_id: str, msg: WSMessage) -> None:
"""Dispatch an RPC request and send the response.
Args:
conn_id: Originating connection ID.
msg: Parsed WSMessage with RPC payload.
"""
method = msg.payload.get("method", "")
params = msg.payload.get("params", {})
try:
result = await self._rpc.dispatch(method, conn_id, params)
if method == "publish":
await self._broadcast_legacy(conn_id, result)
resp = WSMessage(
msg_type=MessageType.RPC_RESPONSE,
payload={"result": result},
correlation_id=msg.msg_id,
)
except RPCError as exc:
resp = WSMessage(
msg_type=MessageType.ERROR,
payload={"error": str(exc)},
correlation_id=msg.msg_id,
)
await self._send_to(conn_id, json.dumps(resp.to_dict()))
@traces(spec="014", story="US-008", reqs=["REQ-014-131"])
@traces(spec="014", story="US-012", reqs=["REQ-014-193"])
async def broadcast(
self,
topic: str,
event_payload: dict[str, Any],
) -> int:
"""Broadcast a JSON-RPC 2.0 notification to subscribers.
Sends a server-push notification (no id field) to all
connections subscribed to the given topic. Includes a
monotonically increasing sequence number per topic.
Args:
topic: Event topic name (e.g. "cxp.traffic").
event_payload: Event data to include in params.
Returns:
Number of clients the event was delivered to.
"""
if not self._pubsub:
return 0
recipients = await self._pubsub.get_matching_connections(
topic, payload=event_payload
)
if not recipients:
return 0
self._sequence_counters[topic] += 1
sequence = self._sequence_counters[topic]
notification = {
"jsonrpc": "2.0",
"method": topic,
"params": {
"sequence": sequence,
"timestamp": datetime.now(UTC).isoformat(),
"payload": event_payload,
},
}
payload_str = json.dumps(notification)
delivered = 0
for cid in recipients:
ws = self._ws_map.get(cid)
if ws:
try:
await ws.send(payload_str)
delivered += 1
message_sent()
except Exception:
logger.exception("Broadcast to %s failed", cid)
return delivered
@traces(spec="014", story="US-011", reqs=["REQ-014-174"])
async def push_to_client(
self,
connection_id: str,
method: str,
params: dict[str, Any],
) -> bool:
"""Send a JSON-RPC notification to a specific client.
Args:
connection_id: Target client connection ID.
method: Notification method name.
params: Notification parameters.
Returns:
True if delivered, False otherwise.
"""
ws = self._ws_map.get(connection_id)
if not ws:
return False
notification = {
"jsonrpc": "2.0",
"method": method,
"params": params,
}
try:
await ws.send(json.dumps(notification))
message_sent()
return True
except Exception:
logger.exception("Push to %s failed", connection_id)
return False
async def _broadcast_legacy(
self,
sender_id: str,
result: dict[str, Any],
) -> None:
"""Push an event to matching subscribers (legacy format).
Args:
sender_id: Connection that published.
result: Publish result with topic, data, recipients.
"""
topic = result.get("topic", "")
data = result.get("data", {})
recipients = result.get("recipients", [])
event = WSMessage(
msg_type=MessageType.EVENT,
payload={"topic": topic, "data": data},
)
payload = json.dumps(event.to_dict())
for cid in recipients:
if cid == sender_id:
continue
ws = self._ws_map.get(cid)
if ws:
try:
await ws.send(payload)
message_sent()
except Exception:
logger.exception("Broadcast to %s failed", cid)
async def _send_to(self, conn_id: str, text: str) -> None:
"""Send text to a specific connection.
Args:
conn_id: Target connection ID.
text: Raw text to send.
"""
ws = self._ws_map.get(conn_id)
if ws:
try:
await ws.send(text)
message_sent()
except Exception:
logger.exception("Send to %s failed", conn_id)