import logging import socket from io import BufferedReader from typing import Tuple from httplib.exceptions import BadRequest BUFSIZE = 4096 TIMEOUT = 3 FORMAT = "UTF-8" MAXLINE = 4096 class HTTPSocket: host: str conn: socket.socket file: Tuple[BufferedReader, None] def __init__(self, conn: socket.socket, host: str): self.host = host self.conn = conn self.conn.settimeout(TIMEOUT) self.conn.setblocking(True) self.conn.settimeout(60) self.file = self.conn.makefile("rb") def close(self): self.file.close() # self.conn.shutdown(socket.SHUT_RDWR) self.conn.close() def is_closed(self): return self.file is None def reset_request(self): self.file.close() self.file = self.conn.makefile("rb") def __do_receive(self): if self.conn.fileno() == -1: raise Exception("Connection closed") result = self.conn.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: buffer = self.file.read(size) else: buffer = self.file.read1(size) if len(buffer) == 0: raise ConnectionAbortedError return buffer def read_line(self): try: line = str(self.read_bytes_line(), FORMAT) except UnicodeDecodeError: # Expected UTF-8 raise BadRequest() return line def read_bytes_line(self) -> bytes: line = self.file.readline(MAXLINE + 1) if len(line) > MAXLINE: raise InvalidResponse("Line too long") elif len(line) == 0: raise ConnectionAbortedError 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