Verified Commit efb3bdb4 authored by Jakob Moser's avatar Jakob Moser
Browse files

Implement Server

parent 8f85bdd3
Loading
Loading
Loading
Loading

poolpay/wire/Server.py

0 → 100644
+65 −0
Original line number Diff line number Diff line
from __future__ import annotations

import json
from collections.abc import Callable
from dataclasses import dataclass, field
from pathlib import Path
from socketserver import BaseServer, StreamRequestHandler, ThreadingUnixStreamServer

from poolpay.wire.Message import Message

# Maps Python's BaseServer instances to our Server instances, because apparently that's the only way a request handler
# can access its surroundings
_servers: dict[BaseServer, Server] = {}


@dataclass
class Server:
    socket_path: Path
    _server: BaseServer = field(init=False)
    _handlers: list[Callable[[Message], None]] = field(default_factory=list, init=False)

    class RequestHandler(StreamRequestHandler):
        def handle(self) -> None:
            # TODO What to do if line is longer than 1024 bytes?
            json_str = self.rfile.readline(1024).strip().decode("utf-8")
            data = json.loads(json_str)

            response = {"status": "received"}
            self.wfile.write((json.dumps(response) + "\n").encode("utf-8"))

            for handler in _servers[self.server]._handlers:
                handler(data)

    def __post_init__(self) -> None:
        self._server = ThreadingUnixStreamServer(
            str(self.socket_path.resolve()),
            Server.RequestHandler,
            bind_and_activate=False,
        )
        _servers[self._server] = self

    def broadcast(self, message: Message) -> None:
        """
        Brodcast the message to all connected clients (might be none)
        """
        raise NotImplementedError()  # TODO

    def on_receive(self, handle_message: Callable[[Message], None]) -> None:
        """
        Register a handler that is called whenever this server receives a message from any of its clients.
        """
        self._handlers.append(handle_message)

    def start(self) -> None:
        """
        Start serving. This method is blocking, i.e. does not return.
        """
        self._server.server_bind()
        self._server.server_activate()
        self._server.serve_forever()

    def stop(self) -> None:
        self._server.shutdown()
        self._server.server_close()
        self.socket_path.unlink()