Source code for musicdb.lib.ws.server

# MusicDB,  a music manager with web-bases UI that focus on music.
# Copyright (C) 2017 - 2021  Ralf Stemmer <ralf.stemmer@gmx.net>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
This module provides the server infrastructure of the server.
"""

from autobahn.asyncio.websocket import WebSocketServerProtocol, WebSocketServerFactory
from musicdb.lib.ws.websocket           import WebSocket, MusicDBWebSocketFactory
from musicdb.lib.ws.mdbwsi              import MusicDBWebSocketInterface
import json
import ssl
import asyncio
import logging

[docs]class MusicDBWebSocketProtocol(WebSocket, MusicDBWebSocketInterface): """ Derived from :class:`musicdb.lib.ws.websocket.WebSocket` and :class:`musicdb.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 :class:`musicdb.lib.ws.mdbwsi.MusicDBWebSocketInterface` by starting the server and accessing it via ``nmap -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 the ``onWSConnect`` method. """ def __init__(self): try: WebSocket.__init__(self) MusicDBWebSocketInterface.__init__(self) except Exception as e: logging.exception("Creating new protocol object failed with exception %s", str(e)) raise e logging.info("New protocol object created for connection.")
[docs]class MusicDBWebSocketServer(object): """ This class implements the whole server infrastructure. Outside of the MusicDB WebSocket Interface abstraction this is the only class to use. """ def __init__(self): self.factory = MusicDBWebSocketFactory() self.factory.protocol = MusicDBWebSocketProtocol self.eventloop = asyncio.get_event_loop() self.coro = None self.server = None self.tlscontext = None
[docs] def Setup(self, address, port, cert, key): """ This method does the server setup. It configures a TLS encrypted connection and creates the event loop. Args: 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, otherwise ``False`` """ logging.info("MusicDB WebSocket Server (\033[0;32mTLS secured\033[1;34m) listening to port \033[1;36m%s", port) #self.tlscontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) # deprecated since Python 3.6 self.tlscontext = ssl.SSLContext(ssl.PROTOCOL_TLS) # Use best supported protocol self.tlscontext.set_ciphers("HIGH:MEDIUM:!aNULL:!MD5") self.tlscontext.verify_mode = ssl.CERT_NONE # no client side cert needed... try: self.tlscontext.load_cert_chain(cert, key) except FileNotFoundError: logging.critical("At least one of the following files are missing:") logging.critical(cert) logging.critical(key) logging.critical("Secure websockets (TLS) not possible!") return False except PermissionError: logging.critical("At least one of the following files are not accessable (Permission Error):") logging.critical(cert) logging.critical(key) logging.critical("Secure websockets (TLS) not possible!") return False try: self.coro = self.eventloop.create_server(self.factory, address, port, ssl=self.tlscontext, ) self.task = self.eventloop.create_task(self.coro) # necessary for run_until_complete except Exception as e: logging.exception("Creating server task failed with exception: %s", str(e)) return False self.eventloop.set_exception_handler(self.ExceptionHandler) return True
def ExceptionHandler(self, loop, context): exception = context["exception"] message = context["message"] logging.warning("Unexpected %s exception in eventloop: %s - %s", type(exception), str(exception), str(message)) return
[docs] def Start(self): """ This method starts the server. Returns: ``True`` on success, otherwise ``False`` Example: .. code-block:: python 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) """ if self.server: logging.warning("Double-Start. Server already running.") return False if not self.tlscontext: logging.warning("You need to setup the server before starting!") return False try: self.server = self.eventloop.run_until_complete(self.task) except Exception as e: logging.exception("Starting server eventloop failed with exception: %s", str(e)) return False return True
[docs] def HandleEvents(self): """ 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: .. code-block:: python while True: server.HandleEvents() if shutdown: server.Stop() break time.sleep(.1) # Avoid high CPU load """ self.eventloop.run_until_complete(self.task)
[docs] def Stop(self): """ This methos halts the server. It closes the connection and the event loop. Returns: ``True`` """ if self.server: self.server.close() # Close the websocket server else: logging.warning("No server running to stop!") logging.debug("Stopping event loop") self.eventloop.run_until_complete(self.eventloop.shutdown_asyncgens()) # TODO: Check if I need this self.eventloop.close() # Close the asyncio event loop self.server = None self.eventloop = None return True
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4