Files
controller-wisetopdataserver/python/biopro/server/websocket.py
T
2021-12-20 14:52:55 +08:00

229 lines
7.0 KiB
Python

from typing import Set, Optional, List
# noinspection PyPackageRequirements
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
from biopro.message.request import DataMessage, DataMessageHandler
from biopro.util.console import CYAN, RED, YELLOW, pc
from biopro.util.logger import LoggerFlag
from .controller import ControlServerAPI
from .socket import ServerThread
__all__ = ['WebsocketThread', 'WebsocketHandler']
class WebsocketThread(ServerThread):
"""Websocket server thread"""
def __init__(self, server: ControlServerAPI, url: str, port: int):
"""
:param server: data server to attach
:param url: socket address
:param port: socket port
"""
super().__init__('WebSocketServer', RED)
# server attribute
self._websocket_url: str = url
self._websocket_port: int = port
self._websocket_server: SimpleWebSocketServer = None
# socket attribute
self.active_client: Set[WebsocketHandler] = set()
# websocket command handler
self._handler = server
@property
def websocket_url(self) -> str:
return self._websocket_url
@property
def websocket_port(self) -> int:
return self._websocket_port
@property
def websocket_address(self) -> str:
return 'ws://%s:%d' % (self._websocket_url, self._websocket_port)
def setup(self) -> None:
super().setup()
self.log_info('start listen %s:%d' % (self._websocket_url, self._websocket_port))
self._websocket_server = server = SimpleWebSocketServer(self._websocket_url,
self._websocket_port,
WebsocketHandler)
# add extra attribute to server
server.biopro_websocket_thread = self
@property
def is_startup(self):
return self._websocket_server is not None and super().is_startup
def shutdown(self) -> None:
self.log_info('close listen %s:%d' % (self._websocket_url, self._websocket_port))
server: SimpleWebSocketServer = self._websocket_server
if server is not None:
server.close()
self._websocket_server = None
def routine(self, event = None) -> None:
self._websocket_server.serveonce()
def add_active_client(self, client: 'WebsocketHandler') -> None:
self.active_client.add(client)
def remove_active_client(self, client: 'WebsocketHandler') -> None:
self.active_client.remove(client)
# noinspection PyShadowingNames
def broadcast_command(self, message: str, device: Optional[int] = None):
if not self.is_closed:
data = DataMessage.response(message)
if device is None:
for client in self.active_client:
client.sendMessage(data)
else:
for client in self.active_client:
if device in client.listen_device:
client.sendMessage(data)
class WebsocketHandler(WebSocket, LoggerFlag):
def __init__(self, server, sock, address):
"""
:param server: websocket server
:param sock: socket
:param address: socket address
"""
WebSocket.__init__(self, server, sock, address)
LoggerFlag.__init__(self, 'WebSocketHandler', YELLOW)
self._websocket: Optional[WebsocketThread] = getattr(server, 'biopro_websocket_thread', None)
self._handler: Optional[ControlServerAPI] = None
self.data_message_handler = WebsocketDataMessageHandler(self)
if self._websocket is not None:
# noinspection PyProtectedMember
self._handler = self._websocket._handler
def handleConnected(self):
"""handle connection"""
self.log_info('connection accept', self.address)
if self._websocket is not None:
self._websocket.add_active_client(self)
def handleClose(self):
"""handle connection close"""
self.log_info('connection close', self.address)
self._handler.data_request_stream(False, self.data_message_handler)
if self._websocket is not None:
self._websocket.remove_active_client(self)
self._handler = None
@property
def is_closed(self) -> bool:
return self._handler is None
# @property
# def file_handle(self) -> Optional[int]:
# return self.data_message_handler.file_handle
@property
def listen_device(self) -> List[int]:
return self.data_message_handler.listen_device
def handleMessage(self):
"""handle websocket message"""
command: str = self.data
if len(command) > 0 and self._handler is not None:
self.log_info('recv', pc(command, CYAN))
try:
self._handle_command(command)
except RuntimeError as e:
self.log_warn(e)
self.sendMessage(DataMessage.response(DataMessage.warn(str(e))))
# print_exception(self)
except BaseException as e:
self.log_warn(e)
self.sendMessage(DataMessage.response(DataMessage.error(str(e))))
# print_exception(self)
def _handle_command(self, command: str):
server: ControlServerAPI = self._handler
if server is None:
return
if command == DataMessage.GREETING:
message = DataMessage.greeting()
elif command == DataMessage.SYNC:
message = server.websocket_command(command)
elif command == DataMessage.START_DATA_STREAM:
server.data_request_stream(True, self.data_message_handler)
return
elif command == DataMessage.STOP_DATA_STREAM:
server.data_request_stream(False, self.data_message_handler)
return
elif command.startswith(DataMessage.SYNC):
message = server.websocket_command(command)
elif command.startswith(DataMessage.BROADCAST):
message = server.websocket_command(command)
else:
server.websocket_data_request(self.data_message_handler, command)
return
if message is None:
return
elif isinstance(message, str):
self.sendMessage(DataMessage.response(message))
elif isinstance(message, bytes):
self.sendMessage(message)
elif isinstance(message, list):
for x in message:
self.sendMessage(DataMessage.response(x))
else:
self.log_warn('unknown message response type')
class WebsocketDataMessageHandler(DataMessageHandler):
__slots__ = '__websocket',
def __init__(self, websocket: WebsocketHandler):
super().__init__()
self.__websocket = websocket
def on_message(self, message: bytes):
# self.__websocket.log_verbose('send', 'bytes', len(message))
self.__websocket.sendMessage(message)
def __str__(self):
return 'DataMessageHandler[%s]' % ('websocket:' + str(self.__websocket.address))