diff --git a/README.md b/README.md index 78a1aec..af5c40e 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,7 @@ - [SqlAlchemy session generator](https://stackoverflow.com/a/71053353) - [Error handling decorator](https://chatgpt.com/share/67a46109-d38c-8005-ac36-677e6511ddcd) + +### Platform specific problems + +- [Windows sends partial data](https://stackoverflow.com/a/31754798) \ No newline at end of file diff --git a/src/bank_node/bank_node.py b/src/bank_node/bank_node.py index 8fa88bc..ec2ed52 100644 --- a/src/bank_node/bank_node.py +++ b/src/bank_node/bank_node.py @@ -46,8 +46,6 @@ class BankNode(): # Handle windows related signals if sys.platform == "win32": - signal.signal(signal.CTRL_C_EVENT, self.gracefully_exit) - signal.signal(signal.CTRL_BREAK_EVENT, self.gracefully_exit) signal.signal(signal.SIGBREAK, self.gracefully_exit) def start(self): @@ -78,6 +76,7 @@ class BankNode(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_server: socket_server.bind((self.config.ip, port)) socket_server.listen() + socket_server.setblocking(True) self.socket_server = socket_server self.logger.info("Listening on %s:%s", self.config.ip, port) diff --git a/src/bank_node/bank_worker.py b/src/bank_node/bank_worker.py index a70b0d9..e97814a 100644 --- a/src/bank_node/bank_worker.py +++ b/src/bank_node/bank_worker.py @@ -8,21 +8,20 @@ import sys from bank_protocol.command_handler import CommandHandler from core import Request, Response, BankNodeConfig from core.exceptions import BankNodeError +from utils.logger import setup_logger class BankWorker(multiprocessing.Process): def __init__(self, client_socket: socket.socket, client_address: Tuple, config: BankNodeConfig): super().__init__() - self.logger = logging.getLogger(__name__) - self.client_socket = client_socket - self.client_socket.settimeout(config.client_idle_timeout) self.client_address = client_address - - self.command_handler = CommandHandler(config) self.config = config + self.logger = None + self.command_handler = None + def __setup_signals(self): self.logger.debug("Setting up exit signal hooks for worker") @@ -32,50 +31,81 @@ class BankWorker(multiprocessing.Process): # Handle windows related signals if sys.platform == "win32": - signal.signal(signal.CTRL_C_EVENT, self.gracefully_exit_worker) - signal.signal(signal.CTRL_BREAK_EVENT, self.gracefully_exit_worker) signal.signal(signal.SIGBREAK, self.gracefully_exit_worker) def run(self): + # Logging behaves weirdly with processes on windows + # and loses its configuration by default + # -> Set it up again in the fresh process + if sys.platform == "win32": + setup_logger(self.config.verbosity) + + self.logger = logging.getLogger(__name__) + self.command_handler = CommandHandler(self.config) + + self.client_socket.settimeout(self.config.client_idle_timeout) + self.client_socket.setblocking(True) + self.__setup_signals() + with self.client_socket: - while True: - try: - raw_request = self.client_socket.recv(1024).decode("utf-8") - - if not raw_request: - self.logger.debug("%s disconnected", self.client_address[0]) - break - - request = Request(raw_request) - self.logger.debug("Received request from %s - %s", self.client_address[0], request) - - response: Response = self.command_handler.execute(request) + "\n\r" - - self.client_socket.sendall(response.encode("utf-8")) - - except socket.timeout: - self.logger.debug("Client was idle for too long. Ending connection") - response = "ER Idle too long\n\r" - self.client_socket.sendall(response.encode("utf-8")) - self.client_socket.shutdown(socket.SHUT_RDWR) - self.client_socket.close() - break - except UnicodeDecodeError: - self.logger.warning("Received a non utf-8 message") - response = "ER Not utf-8 message" - self.client_socket.sendall(response.encode("utf-8")) - break - except BankNodeError as e: - response = "ER " + e.message + "\n\r" - self.client_socket.sendall(response.encode("utf-8")) - except socket.error as e: - self.logger.error(e) - response = "ER Internal server error\n\r" - break + self.serve_client() self.logger.debug("Closing process for %s", self.client_address[0]) + def serve_client(self): + buffer = "" + + while True: + try: + data = self.client_socket.recv(1024).decode("utf-8") + + if not data: + self.logger.debug("%s disconnected", self.client_address[0]) + break + + buffer += data + self.logger.debug("Buffer updated: %r", buffer) + + if "\r\n" in buffer: + self.logger.debug("CRLF detected") + request_data, buffer = buffer.split("\r\n", 1) + elif "\n" in buffer: + self.logger.debug("LF detected") + request_data, buffer = buffer.split("\n", 1) + elif "\r" in buffer: + self.logger.debug("CR detected") + request_data, buffer = buffer.split("\r", 1) + else: + continue + + self.logger.debug("Processing request: %r", request_data) + + request = Request(request_data) + response: Response = self.command_handler.execute(request) + "\r\n" + self.client_socket.sendall(response.encode("utf-8")) + self.logger.debug("Response sent to %s", self.client_address[0]) + + except socket.timeout: + self.logger.debug("Client was idle for too long. Ending connection") + response = "ER Idle too long\n\r" + self.client_socket.sendall(response.encode("utf-8")) + self.client_socket.shutdown(socket.SHUT_RDWR) + self.client_socket.close() + break + except UnicodeDecodeError: + self.logger.warning("Received a non utf-8 message") + response = "ER Not utf-8 message" + self.client_socket.sendall(response.encode("utf-8")) + break + except BankNodeError as e: + response = "ER " + e.message + "\n\r" + self.client_socket.sendall(response.encode("utf-8")) + except socket.error as e: + self.logger.error(e) + response = "ER Internal server error\n\r" + break + def gracefully_exit_worker(self, signum, _): """Log the signal caught and exit with status 0""" diff --git a/src/bank_protocol/bank_scanner.py b/src/bank_protocol/bank_scanner.py index eb025fd..06420b0 100644 --- a/src/bank_protocol/bank_scanner.py +++ b/src/bank_protocol/bank_scanner.py @@ -3,18 +3,22 @@ import socket import threading import logging -class BankScanner(): +from core.peer import Peer + + +class BankScanner(threading.Thread): def __init__(self, host: str, port_start: int, port_end: int): self.logger = logging.getLogger(__name__) self.host = host self.port_start = port_start self.port_end = port_end + self.peer = Peer() def scan(self) -> Optional[socket]: threads = [] for port in range(self.port_start, self.port_end + 1): - t = threading.Thread(target=self.__probe_for_open_ports, args=(self.host, port,), name=f"ScannerThread-{self.host}:{port}") + t = threading.Thread(target=self.__probe_for_open_ports, args=(self.host, port,), name=f"BankScannerThread-{self.host}:{port}") threads.append(t) def __probe_for_open_ports(self, host: str, port: int) -> Optional[socket]: diff --git a/src/bank_protocol/command_handler.py b/src/bank_protocol/command_handler.py index 61728f3..60e6b51 100644 --- a/src/bank_protocol/command_handler.py +++ b/src/bank_protocol/command_handler.py @@ -36,6 +36,8 @@ class CommandHandler: self.logger.warning("Unknown command %s", request.command_code) raise BankNodeError(f"Unknown command {request.command_code}") + self.logger.debug("Serving %s", request.command_code) + command = self.registered_commands[request.command_code] try: response = command(request, self.config) diff --git a/src/core/peer.py b/src/core/peer.py index e69de29..ed6fd85 100644 --- a/src/core/peer.py +++ b/src/core/peer.py @@ -0,0 +1,2 @@ +class Peer(): + pass \ No newline at end of file diff --git a/src/core/request.py b/src/core/request.py index 0bf9c43..5cfa83d 100644 --- a/src/core/request.py +++ b/src/core/request.py @@ -1,15 +1,20 @@ import re from bank_protocol.exceptions import InvalidRequest +import logging class Request(): def __init__(self, raw_request: str): + logger = logging.getLogger(__name__) + if re.match(r"^[A-Z]{2}$", raw_request): - self.command_code = raw_request[0:2] # Still take the first 2 characters, because of lingering crlf + logger.debug("Found 2 char command") + self.command_code = raw_request[0:2] # Still take the first 2 characters, because of lingering crlf self.body = None elif re.match(r"^[A-Z]{2} .+", raw_request): + logger.debug("Found command with arguments") command_code: str = raw_request[0:2] body: str = raw_request[3:-1] or "" diff --git a/src/utils/logger.py b/src/utils/logger.py index c21ab50..212e325 100644 --- a/src/utils/logger.py +++ b/src/utils/logger.py @@ -4,7 +4,7 @@ import logging def setup_logger(verbosity: str): if verbosity == "DEBUG": - log_format = "[ %(levelname)s / %(processName)s ] - %(name)s:%(lineno)d - %(message)s" + log_format = "[ %(levelname)s / %(processName)s ] - %(module)s/%(filename)s:%(lineno)d - %(message)s" else: log_format = "[ %(levelname)s ] - %(message)s"