import logging from abc import ABC, abstractmethod from urllib.parse import urlparse from client.ResponseHandler import ResponseHandler from client.httpclient import FORMAT, HTTPClient, InvalidResponse, InvalidStatusLine, UnsupportedEncoding class AbstractCommand(ABC): def __init__(self, url: str, port: str): self.url = url self.port = port @property @abstractmethod def command(self): pass @staticmethod def create(command: str, url: str, port: str): if command == "GET": return GetCommand(url, port) elif command == "HEAD": return HeadCommand(url, port) elif command == "POST": return PostCommand(url, port) elif command == "PUT": return PutCommand(url, port) else: raise ValueError() def execute(self): (host, path) = self.parse_uri() client = HTTPClient(host) client.connect((host, int(self.port))) message = f"{self.command} {path} HTTP/1.1\r\n" message += f"Host: {host}\r\n" message += "Accept: */*\r\nAccept-Encoding: identity\r\n" encoded_msg = self._build_message(message) logging.info("---request begin---\r\n%s---request end---", encoded_msg.decode(FORMAT)) logging.debug("Sending HTTP message: %r", encoded_msg) client.sendall(encoded_msg) logging.info("HTTP request sent, awaiting response...") try: self._await_response(client) except InvalidResponse as e: logging.debug("Internal error: Response could not be parsed", exc_info=e) return except InvalidStatusLine as e: logging.debug("Internal error: Invalid status-line in response", exc_info=e) return except UnsupportedEncoding as e: logging.debug("Internal error: Unsupported encoding in response", exc_info=e) finally: client.close() def _await_response(self, client): while True: line = client.read_line() print(line, end="") if line in ("\r\n", "\n", ""): break def _build_message(self, message: str) -> bytes: return (message + "\r\n").encode(FORMAT) def parse_uri(self): parsed = urlparse(self.url) # If there is no netloc, the url is invalid, so prepend `//` and try again if parsed.netloc == "": parsed = urlparse("//" + self.url) host = parsed.netloc path = parsed.path if len(path) == 0 or path[0] != '/': path = "/" + path port_pos = host.find(":") if port_pos >= 0: host = host[:port_pos] return host, path class AbstractWithBodyCommand(AbstractCommand, ABC): def _build_message(self, message: str) -> bytes: body = input(f"Enter {self.command} data: ").encode(FORMAT) print() message += "Content-Type: text/plain\r\n" message += f"Content-Length: {len(body)}\r\n" message += "\r\n" message = message.encode(FORMAT) message += body message += b"\r\n" return message class HeadCommand(AbstractCommand): @property def command(self): return "HEAD" class GetCommand(AbstractCommand): @property def command(self): return "GET" def _await_response(self, client): (version, status, msg) = ResponseHandler.get_status_line(client) logging.debug("Parsed status-line: version: %s, status: %s", version, status) headers = ResponseHandler.get_headers(client) logging.debug("Parsed headers: %r", headers) handler = ResponseHandler.create(client, headers, status, self.url) handler.handle() class PostCommand(AbstractWithBodyCommand): @property def command(self): return "POST" class PutCommand(AbstractWithBodyCommand): @property def command(self): return "PUT"