Skip to main content

A simple websocket/msgpack messaging library that provides pub/sub and rpc patterns.

Project description

wsmsg Package

Overview

Websockets are used, and the server will accept all authenticated connections to an endpoint root (default '/'). Authentication is via an HTTP bearer token on initial connection. Each node attaches to an endpoint specified in the connection URI. For example: wss://<host>:<port>/<endpoint>. Channels receive (and queue) messages. Subscriptions bind an endpoint to one or more channels. Subjects can be used to filter messages in a subscription. Message delivery is at most once, with flow control and no guaranteed persistence.

Flow Diagram

Publish Channel  Routing     Endpoint 
-------+--------+-----------+-------------
      / Queue -> Forward \ / Queue -> Node
Node <  Queue -> Forward  X         / Node
      \ Queue -> Forward / \ Queue <  Node
                                    \ Node

Message Format

Messages are sent/received as a msgpack array

  • uid: bytes
    Binary-safe unique id of message - recommand ULID as bytes
  • channel: str
    Channel this message will be sent to
  • subject: str
    Subject of message
  • reference: bytes
    Binary-safe id of message that this references
  • data: any
    Subject-defined message data

Python

Message = NamedTuple('Message', uid=bytes, channel=str, subject=str, reference=bytes, data=any)

class Message(NamedTuple):
    uid: bytes
    channel: str
    subject: str
    reference: bytes
    data: any
    def __repr__(self):
        elements = [
            str(ulid.from_bytes(self.uid)),
            self.channel,
            self.subject,
            str(ulid.from_bytes(self.reference)) if self.reference else None,
            repr(self.data)
        ]
        return "Message(%s)" % ', '.join(elements)

System Messages

The 'system' channel has ultimate priority (100) over all other channels.
If there are any queued messages in the system queue, no other channels will forward.

Commands

  • ack
    Acknowledge message receipt, not forwarded.
    Ack'd message uid should be in the refercence field.
    Next message for connection will not be dequeued or sent
    until previous message was ackowledged.
  • subcribe
    • channel (endpoint: str, channel_pattern: str, subject_pattern: str)
      Subscribe endpoint to channel pattern with subject pattern filter.
    • message (endpoint: str, message_uid: bytes, expires: int)
      Subscribe endpoint to any messages that reference the message uid until the expiration (unix timestamp).
  • unsubscribe (endpoint: str, channel_pattern: str)
    • channel (endpoint: str, channel_pattern: str)
      Unsubscribe endpoint from patterns matching pattern.
      Includes any subject filters.
    • message (endpoint: str, message_uid: bytes)
      Unsubscribe endpoint to any messages that reference the message uid.
  • pause
    Pause all message transmissions.
    Messages can still be received and system commands processed.
  • resume
    Resume message transmissions.
  • stop
    Cleanly stop and shutdown server.
    Pending messages are persisted.
  • channel
    • create (channel: str)
      Create channel queue.
    • size (channel: str, size: int)
      Set max size of channel queue. Old messages are dropped when full
    • priority (channel: str, priority: int)
      Set channel priority. Higher is better, range 0-99. Default of zero.
  • endpoint
    • create (endpoint: str)
      Create endpoint queue
    • size (endpoint: str, size: int)
      Set max size of endpoint queue. Old messages are dropped when full

System Events

  • stopping Server is shutting down (in one second), no more messages will be delivered
  • node
    • joined (endpoint, uid)
    • left (endpoint, uid)
  • endpoint
    • created (endpoint)
    • sized (channel: str)
  • channel
    • created (channel: str)
    • sized (channel: str)
    • prioritized (channel: str)

Misc

Router

  • Until stop
    • Wait for next message
    • For each subscriber
      • Push message to node Queue

Route Map?

class Route NamedTuple('Route', router=Router, dest=set[UUID])
RouteMap = dict[UUID, Route]

routes: RouteMap = 
{
    <channel>: <route>, 
    ...
}

while True:
    msg = channel.get()
    for node in routes[channel].dest:
      forward_message(msg, node)

Examples

Task Queue

By connecting all nodes to one endpoint, each node will get a fair shair of the messages in the 'order' channel with the subject 'submitted'.

All nodes connects to ws://<host>:<port>/group1
node1 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, ('group1', 'orders', 'submitted'))
node2 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, ('group1', 'orders', 'submitted'))
node3 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, ('group1', 'orders', 'submitted'))

Fanout

By using one endpoint per node, each node will get a copy of the messages in the 'order' channel with the subject 'submitted'.

Each node connects to ws://<host>:<port>/<node id>
node1 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'orders', 'submitted'))
node2 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'orders', 'submitted'))
node3 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'orders', 'submitted'))

Direct Delivery

If each node subscribes to a unique addressable channel, messages can be routed to each node by the sender.
To send a message to a single node, use channel 'node.' with any subject.
To send a message to all nodes, additionally subscribe all nodes to a 'nodes' channel and send to that.

Each node connects to ws://<host>:<port>/<node id>
node1 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'node.<node id>', '*'))
node2 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'node.<node id>', '*'))
node3 -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<node id>, 'node.<node id>', '*'))

RPC

Advantages: Less messages to send, supports heavy RPC traffic through server.
Disadvantages: Need to know what reply channel and subjects will be used, large number of channel subscriptions can slow down message forwarding.

Connect to ws://<host>:<port>/<id>
node -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<id>, 'order', 'created'))
node -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<id>, 'order', 'exception'))
...
# Generate uid
Register Queue with RX task/thread for reference=uid
node -> Message(uid, 'order', 'submitted', None, None, Check)
...
node <- Message(<message uid>, 'order', 'created', uid, None, <check uid>)
RX task/thread matches and puts message(s) in Queue
...
Get result(s) with timeout from Queue
Resend if needed or show error on exception
Unregister Queue from RX task/thread

RPC - Streamlined

Advantages: message subscription matching is much faster, RTT should be lower. No need to know what reply subject may be.
Disadvantages: more messages to send, total throughput may be lower if heavy use of RPC.
Notes: If you set the message subscription expiration, no need to unsubscribe.

server -> Message(ulid.new().bytes, 'system', 'subscribe.channel', None, (<endpoint>, 'rpc', 'order.*'))
server -> Message(ulid.new().bytes, 'system', 'channel.priority', None, ('rpc', 99))
server -> Message(ulid.new().bytes, 'system', 'endpoint.size', None, (<endpoint>, 10))
...
Connect to ws://<host>:<port>/<id>
...
Generate uid
Register Queue with RX task/thread for reference=uid
node -> Message(ulid.new().bytes, 'system', 'subscribe.message', None, (<id>, uid, <timeout>))
node -> Message(uid, 'rpc', 'order.get.request', None, Check)
...
node <- Message(<message uid>, 'rpc', 'order.get.reply', uid, None, <check uid>)
RX task/thread matches and puts message(s) in Queue
...
Get result(s) with timeout from Queue
Resend if needed or show error on exception
Unregister Queue from RX task/thread

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wsmsg-0.15.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wsmsg-0.15-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file wsmsg-0.15.tar.gz.

File metadata

  • Download URL: wsmsg-0.15.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/33.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.2

File hashes

Hashes for wsmsg-0.15.tar.gz
Algorithm Hash digest
SHA256 cf55aaed12d859d06a7fe70aff1400fdded3f745852be1ebe01cde0ba50e3e76
MD5 2c6767c5275efa1eeab0771342e55964
BLAKE2b-256 7362ca1681a3fbade4d25c90a89fe222c2a6f78a3dd7c0567c95964f85e295c6

See more details on using hashes here.

File details

Details for the file wsmsg-0.15-py3-none-any.whl.

File metadata

  • Download URL: wsmsg-0.15-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/33.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.2

File hashes

Hashes for wsmsg-0.15-py3-none-any.whl
Algorithm Hash digest
SHA256 c7fbdbf02d958c4acd8569f12d40e5846563d64a3394c2c8889b5e6ce9d8dd46
MD5 fd50b45bc81b6aa3721d18caf12152e7
BLAKE2b-256 12eb47bf935b6c4a1f82280d4f22b46c43712c5afc14568eb54808c7226f3552

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page