import logging import socket from io import BufferedReader BUFSIZE = 4096 TIMEOUT = 3 FORMAT = "UTF-8" MAXLINE = 4096 class HTTPClient(socket.socket): host: str file: BufferedReader def __init__(self, host: str): super().__init__(socket.AF_INET, socket.SOCK_STREAM) self.settimeout(TIMEOUT) self.host = host self.setblocking(True) self.settimeout(3.0) self.file = self.makefile("rb") def close(self): self.file.close() super().close() def reset_request(self): self.file.close() self.file = self.makefile("rb") def __do_receive(self): if self.fileno() == -1: raise Exception("Connection closed") result = self.recv(BUFSIZE) return result def receive(self): """Receive data from the client up to BUFSIZE """ count = 0 while True: count += 1 try: return self.__do_receive() except socket.timeout: logging.debug("Socket receive timed out after %s seconds", TIMEOUT) if count == 3: break logging.debug("Retrying %s", count) logging.debug("Timed out after waiting %s seconds for response", TIMEOUT * count) raise TimeoutError("Request timed out") def read(self, size=BUFSIZE, blocking=True) -> bytes: if blocking: return self.file.read(size) return self.file.read1(size) def read_line(self): return str(self.read_bytes_line(), FORMAT) def read_bytes_line(self): """ :rtype: bytes """ line = self.file.readline(MAXLINE + 1) if len(line) > MAXLINE: raise InvalidResponse("Line too long") return line class HTTPException(Exception): """ Base class for HTTP exceptions """ class InvalidResponse(HTTPException): """ Response message cannot be parsed """ def __init(self, message): self.message = message class InvalidStatusLine(HTTPException): """ Response status line is invalid """ def __init(self, line): self.line = line class UnsupportedEncoding(HTTPException): """ Reponse Encoding not support """ def __init(self, enc_type, encoding): self.enc_type = enc_type self.encoding = encoding class IncompleteResponse(HTTPException): def __init(self, cause): self.cause = cause