MusicDB Websocket Protocol¶
There are lots of classes involved to implement the websocket server. The image below shows the whole class diagram. Only the more important methods are included in the diragram. For a full list of all methods see the related documentation of the class. As library I use Autobahn. The class WebSocketServerProtocol and WebSocketServerFactory are base classes from that library. The MusicDBWebSocketInterface implements the API for the JavaScript clients and is documented under MusicDB Websocket API.
In the following pictures the blue elements are related to the musicdb.lib.ws.websocket.WebSocket
class.
This class abstracts the internals of the Autobahn module and provides a low level communication websocket interface.
The red elements to musicdb.lib.ws.mdbwsi.MusicDBWebSocketInterface
that implements the high level API the web clients uses.
The class diagram below shows the Autobahn classes WebSocketServerProtocol and WebSocketServerFactory and how they are related to the MusicDB classes for the websocket communication. In this diagram, only the most relavant methods and attributes are mentioned.
The relation between the Socket Server, Socket Factory and Socket Protocol is a bit insane. The following code may make it a bit more understandable what the Autobahn library wants me to do:
class MusicDBWebSocketServer(object): def __init__(self): self.factory = MusicDBWebSocketFactory() self.factory.protocol = MusicDBWebSocketProtocol
The methods onWSDisconnect
and onWSConnect
are called from musicdb.lib.ws.websocket.WebSocket
and implemented in musicdb.lib.ws.mdbwsi.MusicDBWebSocketInterface
.
They are used to register the callback functions for the musicdb.mdbapi.stream
and musicdb.mdbapi.songqueue
module.
The following state machine shows how a connections is processed.
The musicdb.lib.ws.websocket.WebSocket.SendPacket()
and musicdb.lib.ws.websocket.WebSocket.BroadcastPacket()
method can be called independent from the clients requests.
Just be sure the onWSConnect
method was called before.
Otherwise the mechanics behind won’t work.
Websocket Server¶
This module provides the server infrastructure of the server.
- class musicdb.lib.ws.server.MusicDBWebSocketProtocol[source]¶
Derived from
musicdb.lib.ws.websocket.WebSocket
andmusicdb.lib.ws.mdbwsi.MusicDBWebSocketInterface
. Connecting low level implementation with the high level implementation of MusicDBs WebSocket Interface.This object gets always instatiated when a new client connects to the server. It does not matter if this is a websocket client or not!. To initialize code that needs a working websocket connection, use the
onWSConnect
callback interface.You can and should check changes in
musicdb.lib.ws.mdbwsi.MusicDBWebSocketInterface
by starting the server and accessing it vianmap -p $MDBServerPort localhost
or by accessing the server via https from your browser. Both are not valid websocket connections. The server should create a new connection but will not call theonWSConnect
method.
- class musicdb.lib.ws.server.MusicDBWebSocketServer[source]¶
This class implements the whole server infrastructure. Outside of the MusicDB WebSocket Interface abstraction this is the only class to use.
- HandleEvents()[source]¶
This method handles the events inside the Autobahn internal event loop. It should be called in a loop as long as the server shall work.
- Returns
Nothing
Example
while True: server.HandleEvents() if shutdown: server.Stop() break time.sleep(.1) # Avoid high CPU load
- Setup(address, port, cert, key)[source]¶
This method does the server setup.
It configures a TLS encrypted connection and creates the event loop.
- Parameters
address (str) – address to bind to
port (int) – port to bind to
cert (str) – Path to an SSL certificate
key (str) – Path to the key for the certificate
- Returns
True
on success, otherwiseFalse
- Start()[source]¶
This method starts the server.
- Returns
True
on success, otherwiseFalse
Example
server = MusicDBWebSocketServer() retval = tlswsserver.Setup("127.0.0.1", 9000, "/etc/ssl/test/test.cert", "/etc/ssl/test/test.key") if retval == False: print("Setup for TLS-Server failed!") exit(1) retval = tlswsserver.Start() if retval == False: print("Starting TLS-Server failed!") exit(1)
Websocket Protocol¶
This module provides low level classes for the WebSocket communication to the WebUI.
Most interesting are the following methods:
- class musicdb.lib.ws.websocket.MusicDBWebSocketFactory[source]¶
Derived from
WebSocketServerFactory
. Implements some basic configuration and a broadcasting infrastructure to send packets to all connected clients.- AddToBroadcast(client)[source]¶
This method registers a new client. The client must be of the low level class
musicdb.lib.ws.websocket.WebSocket
or a derived class.- Parameters
client – WebSocket connection handler
- Returns
Nothing
- BroadcastPacket(packet)[source]¶
This method broadcasts a packet to all connected clients.
The
method
value in the packet gets forced to"broadcast"
.- Parameters
packet – A packet dictionary that shall be send to all clients
- Returns
Nothing
- class musicdb.lib.ws.websocket.WebSocket[source]¶
Derived from
WebSocketServerProtocol
. This class provides the low level WebSocket interface for MusicDBs WebSocket Interface. It manages the packet handling and handles callback routines.- BeautifyValues(packet, affectkey, old, new)[source]¶
This method can be used to beautify values inside nested dictionaries. Use-cases are replacing Division Slashes by normal slashes or hyphens by n-dashes in song names.
It recursively scans through nested dictionaries and lists (
packet
) to find a specific key (affectkey
). The content behind that key, in case it is of type string, gets scanned forold
substring. This substring then gets replaced by the substirngnew
For easy to read code, the method returns the reference to
packet
. Keep in mind that the content of packet will be changed even if the return value gets not assigned.The core algorithm was copied from sotapme @ stackoverflow <https://stackoverflow.com/questions/14882138/replace-value-in-json-file-for-key-which-can-be-nested-by-n-levels/14882688>:
- Parameters
packet – A nested dictionary/list
affectkey (str) – A key to search for
old (str) – A sub string to search for, inside the value referenced by
affectkey
new (str) – A sub string to replace
old
- Returns
A reference to
packet
- Raises
TypeError – If
affectkey
,old
ornew
are not of type string
Example
Replace divison slash (U+2215) by slash and hyphen by n-dash
packet = self.BeautifyValues(packet, "name", "∕", "/"); packet = self.BeautifyValues(packet, "name", " - ", " – ");
- BroadcastPacket(packet)[source]¶
This method works line
SendPacket()
only that the packet gets send to all clients. It uses the broadcasting method frommusicdb.lib.ws.websocket.MusicDBWebSocketFactory.BroadcastPacket()
This method should be used with care because it can cause high traffic.- Parameters
packet – A packet dictionary that will be send to all clients
- Returns
Nothing
- SendPacket(packet)[source]¶
This method sends a packet via to the connected client. The format of the packet is described in the MusicDB Websocket API documentation.
The abstract process of sending the packet is shown in the following code:
#packet = self.BeautifyValues(packet, "name", "∕", "/"); rawdata = json.dumps(packet) # Python Dict to JSON string rawdata = rawdata.encode("utf-8") # Encode as UTF-8 self.sendMessage(rawdata, False) # isBinary = False
There is a race condition allowing calling
SendPacket
before the connection process is complete. To prevent problems, this method returnsFalse
if the connection is not established yet. Further more the state of the connection gets checked. If the connection state is not OPEN,False
gets returned, too.All values behind the
name
key of the packet get beautified. It is assumed that those values are used to display information to the user.- Parameters
packet – A packet dictionary that will be send to the client
- Returns
True
on success, otherwiseFalse
- Raises
TypeError – If packet is not of type
dict
RuntimeError – If Autobahns
WebSocketServerProtocol
class did not set an internal state. Should never happen, but happed once :)
Example
Response to an album request by a client.
response = {} response["method"] = "response" response["fncname"] = "GetAlbums" response["fncsig"] = request["fncsig"] response["arguments"] = albums response["pass"] = request["pass"] self.SendPacket(response)
- onClose(wasClean, code, reason)[source]¶
This method gets called by
WebSocketServerProtocol
implementation. It removes this connection to the broadcasting infrastructure ofmusicdb.lib.ws.websocket.MusicDBWebSocketFactory
.This method calls an
onWSDisconnect(wasClean:bool, closecode:int, closereason:str)
Method that must be implemented by the programmer who uses this class. TheonWSDisconnect
method gets only called when there was a successful connection before!
- onCloseHandshakeTimeout()[source]¶
We expected the peer to respond to us initiating a close handshake. It didn’t respond (in time self.closeHandshakeTimeout) with a close response frame though. So we drop the connection, but set self.wasClean = False.
- onConnect(request)[source]¶
Just prints the IP address of the connecting client. See for details. ConnectionRequest in the Autobahn documentation for details.
- Returns
Nothing
- onMessage(payload, isBinary)[source]¶
This method gets called by
WebSocketServerProtocol
implementation. It handles the decoding of a received packet and provides the packet format as described in the MusicDB Websocket API documentation.The decoding process can be abstracted as listed in the following code:
# Check if payload is text if isBinary == True: return None # Create packet rawdata = payload.decode("utf-8") packet = json.loads(rawdata) # Provide packet to high level interface self.onCall(packet)
- Parameters
payload – The payload of a WebSocket message received from a client
isBinary –
True
if binary data got received, False`` when text. This implementation allows only text.
- Returns
None
- onOpen()[source]¶
This method gets called by
WebSocketServerProtocol
implementation. It registers this connection to the broadcasting infrastructure ofmusicdb.lib.ws.websocket.MusicDBWebSocketFactory
.This method calls an
onWSConnect()
Method that must be implemented by the programmer who uses this class.