-
Notifications
You must be signed in to change notification settings - Fork 5
How to create a new transport for Heral
Creating a new transport for Herald boils down to providing two services: herald.transport
and herald.transport.directory
. These services are usually implemented by two separate components.
A component providing such a service must implement the following methods:
This method addresses a message to a given peer. It has the following signature:
fire(self, peer: herald.bean.Peer, message: heard.bean.Message, extra: object = None)
.
The method takes the peer object as a parameter, this object can be None
in case of a replay to a newly discovered peer. It also takes the message to be send, this message must be serialised in a way to be sent by the underling protocol, see Serialisation below. An extra
parameter is provided which holds, whenever relevant, information about the original message in case of a reply.
This method must resolve the address to whom the message is to be sent. This is done based on information encoded in extra
, or extracted based on the peer
object. For instance, in HTTP a peer's address is the set of its hostname, port number, and resource path.
In order for the destination address to be solvable, peer
and extra
cannot be both None
.
A typical implementation of fire()
can go as follows.
def fire(self, peer, message, extra=None):
address = extra['address'] if extra else self.__addresses[peer.uid]
content = serialise(message, address=address)
self.__send_to_address(address, content)
This method broadcasts a message to a given group. It has the following signature:
-
fire_group(self, group: str, peers: Set[herald.bean.Peer], message: heard.bean.Message) -> Set[herald.bean.Peer]
.
The method takes into parameter the group's name, it takes as well as set of peers belonging to that group. Depending on the implemented protocol, the method can either use broadcast, multicast, or publish utilities to deliver the message to the whole group at once, or iterate along the group's member, delivering the message to one at a time. The method returns the set of peers to whom the message was sent.
Like fire()
, fire_group()
must serialise message
in a way that suits the underling transport protocol.
A typical implementation of fire_group()
may look like this.
def fire_group(self, group, peers, message):
content = serialise(message, group=group)
peers = self.__sent_to_group(group, content)
return peers
The transport layer should also handle incoming messages to allow Herald communications. To this end the transport client should either listen to a given port, pull messages regularly from a server, or subscribe to certain events in order to receive messages address to it or to one of its groups.
When a message is received, it is first unserialised to create a herald.beans.ReceivedMessage
object.
Once a message object is created, extra protocol-dependent properties can be loaded into the message, typically the extra
property, which should contain information about the senders address. This is important during the discovery handshake where peers still don't know each others and have no means of guessing the reply address. The message is typically tagged by its transport method at this stage as well. This is done by means of an access ID.
Finally, and depending on the message subject, this method routes the message either to a PeerContact
object, responsible of a 3-phase handshake protocol, or to the Herald core service.
Typically, a transport component should have a message-received call back that looks like follows.
def on_message_received(self, content):
message = unserialise(content)
sender = message.get_header(herald.MESSAGE_HEADER_SENDER_UID)
subject = message.subject
message.set_extra(sender)
message.set_access(ACCESS_ID)
if subject.startswith(SUBJECT_DISCOVERY_PREFIX):
self.__contact.herald_message(self._herald, message)
else:
self._herald.handle_message(message)
Herald uses JSON to serialise messages (message meta-data, headers and content). Serialisation is typically done by calling method herald.utils.to_json()
on a herald.beans.Message
object, after certain headers are set (such as sender UID). Deserialising a JSON-encoded message is done by calling herald.utils.from_json()
on the string object.
A unique ID identifying the underling protocol.
- Property key:
herald.access.id
. - Property type: String.
The service may define more protocol specific properties, such as port, host name, or quality of services provided.
Such a service must implement the following methods:
Method has the following signature:
load_access(self, data: object) -> object
.
It unserializes an object that contains enough information to locate a peer using the underling protocol. For instance, for HTTP such information is the host address, port number, and path.
Method has the following signature:
peer_access_set(self, peer: herald.bean.Peer, data: object)
.
This is a callback, invoked by Herald whenever a peer's access is set. data
is the new access object.
Method has the following signature:
peer_access_unset(self, peer: herald.bean.Peer, data: object)
.
This is a callback, invoked by Herald whenever a peer's access is unset.