From: Benjamin Braatz Date: Fri, 5 Mar 2021 00:35:46 +0000 (+0100) Subject: Move controlpi.plugins to controlpi-plugins X-Git-Tag: v0.3.0~48 X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=476e617c835fd913f02271421437ac42dd068395;p=graphit%2Fcontrolpi-wsserver.git Move controlpi.plugins to controlpi-plugins Namespace packages should not be inside regular packages: https://stackoverflow.com/a/62992832 --- diff --git a/controlpi-plugins/wsserver.py b/controlpi-plugins/wsserver.py new file mode 100644 index 0000000..b431d3e --- /dev/null +++ b/controlpi-plugins/wsserver.py @@ -0,0 +1,96 @@ +"""Provide … + +TODO: documentation, doctests, resilient conf-parsing +""" +import os +import json +import websockets +from websockets import WebSocketServerProtocol, ConnectionClosedError +from websockets.http import Headers +from http import HTTPStatus +from typing import Union, Optional, Tuple, Iterable, Mapping + +from controlpi import BasePlugin, MessageBus, Message, PluginConfiguration + + +class Connection: + def __init__(self, bus: MessageBus, websocket: WebSocketServerProtocol, + path: str) -> None: + self._bus = bus + self._websocket = websocket + address = self._websocket.remote_address + self._address = address[0] + self._port = address[1] + self._name = f"{self._address}:{self._port}" + if path != '/': + self._name = path[1:] + self._bus.register(self._name, [{}], [{}], self._receive) + + async def _receive(self, message: Message) -> None: + json_message = json.dumps(message) + await self._websocket.send(json_message) + + async def run(self): + await self._bus.send({'sender': self._name, + 'event': 'connection opened', + 'address': self._address, + 'port': self._port}) + try: + async for json_message in self._websocket: + original_message = json.loads(json_message) + message = {'sender': self._name} + message.update(original_message) + self._bus.send(message) + except ConnectionClosedError: + pass + await self._bus.send({'sender': self._name, + 'event': 'connection closed'}) + + +Response = Optional[Tuple[HTTPStatus, Headers, bytes]] + + +class WSServer(BasePlugin): + async def _handler(self, websocket: WebSocketServerProtocol, + path: str) -> None: + connection = Connection(self._bus, websocket, path) + await connection.run() + + async def _process_request(self, path: str, + request_headers: Headers) -> Response: + if 'Upgrade' in request_headers: + return None + if path == '/': + path = '/index.html' + response_headers = Headers() + response_headers['Server'] = 'consolepi-wsserver websocket server' + response_headers['Connection'] = 'close' + file_path = os.path.realpath(os.path.join(self._web_root, path[1:])) + if os.path.commonpath((self._web_root, file_path)) != self._web_root \ + or not os.path.exists(file_path) \ + or not os.path.isfile(file_path): + return HTTPStatus.NOT_FOUND, response_headers, b'File not found!' + mime_type = 'application/octet-stream' + extension = file_path.split('.')[-1] + if extension == 'html': + mime_type = 'text/html' + elif extension == 'js': + mime_type = 'text/javascript' + elif extension == 'css': + mime_type = 'text/css' + response_headers['Content-Type'] = mime_type + body = open(file_path, 'rb').read() + response_headers['Content-Length'] = str(len(body)) + return HTTPStatus.OK, response_headers, body + + def _process_conf(self, conf: PluginConfiguration) -> None: + self._port = conf['port'] + self._web_root = os.path.realpath(os.path.join(os.getcwd(), + conf['web root'])) + super()._process_conf(conf) + + async def run(self) -> None: + await super().run() + await websockets.serve(self._handler, port=self._port, + process_request=self._process_request) + print(f"WSServer '{self._name}' serving on port {self._port}.") diff --git a/controlpi/plugins/wsserver.py b/controlpi/plugins/wsserver.py deleted file mode 100644 index c876b13..0000000 --- a/controlpi/plugins/wsserver.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Provide … - -TODO: documentation, doctests -""" -from controlpi import BasePlugin, Message, PluginConfiguration - - -class WSServer(BasePlugin): - async def _receive(self, message: Message) -> None: - send_message = {'sender': self._name} - await self._bus.send(send_message) - - def _process_conf(self, conf: PluginConfiguration) -> None: - self._bus.register(self._name, sends, receives, self._receive) - super()._process_conf(conf) - - async def run(self) -> None: - await super().run() - await self._bus.send({'sender': self._name}) diff --git a/setup.py b/setup.py index d411426..6570a5c 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setuptools.setup( long_description=long_description, long_description_content_type="text/markdown", url="http://docs.graph-it.com/graphit/controlpi-wsserver", - packages=["controlpi.plugins"], + packages=["controlpi-plugins"], install_requires=[ "websockets", "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git",