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.16.tar.gz (14.1 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.16-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: wsmsg-0.16.tar.gz
  • Upload date:
  • Size: 14.1 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.16.tar.gz
Algorithm Hash digest
SHA256 4c55e01426b4cd4cfb09669a1785e81006bc253ea5819d9676b2cd5a8bc2065b
MD5 88e4a0cd5709c80c734c20c5c47ed5fa
BLAKE2b-256 60c0498d6576915e1efa4b720e60f8671dfb31dc28f737f3c14069b1f4d1a82e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: wsmsg-0.16-py3-none-any.whl
  • Upload date:
  • Size: 13.0 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.16-py3-none-any.whl
Algorithm Hash digest
SHA256 fbf8b633e0661d5c4c115f59517a911dabbd832103eb6fad3c35d722bee928b3
MD5 997a322ee863321a81456056d434dd41
BLAKE2b-256 4975ff62dbfe9b0322288a9913264d3757c948249fd86c05a69fdb175b237250

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