Fix some issues, improve documentation

This commit is contained in:
2021-03-27 19:05:09 +01:00
parent 0f7d67c98d
commit 9036755a62
8 changed files with 89 additions and 48 deletions

View File

@@ -34,8 +34,10 @@ class HTTPServerException(Exception):
status_code: str
message: str
body: str
arg: str
def __init__(self, body=""):
def __init__(self, arg, body=""):
self.arg = arg
self.body = body

View File

@@ -7,6 +7,9 @@ from httplib.httpsocket import HTTPSocket, BUFSIZE
class Retriever(ABC):
"""
This is a helper class for retrieving HTTP messages.
"""
client: HTTPSocket
def __init__(self, client: HTTPSocket):
@@ -14,10 +17,23 @@ class Retriever(ABC):
@abstractmethod
def retrieve(self):
"""
Creates an iterator of the retrieved message content.
"""
pass
@staticmethod
def create(client: HTTPSocket, headers: Dict[str, str]):
"""
Creates a Retriever instance depending on the give headers.
@param client: the socket to retrieve from
@param headers: the message headers for choosing the retriever instance
@return: ChunkedRetriever if the message uses chunked encoding, ContentLengthRetriever if the message
specifies a content-length, RawRetriever if none of the above is True.
@raise UnsupportedEncoding: if the `transfer-encoding` is not supported or if the `content-encoding` is not
supported.
"""
# only chunked transfer-encoding is supported
transfer_encoding = headers.get("transfer-encoding")
@@ -32,17 +48,20 @@ class Retriever(ABC):
if chunked:
return ChunkedRetriever(client)
else:
content_length = headers.get("content-length")
if not content_length:
logging.warning("Transfer-encoding and content-length not specified, trying without")
return RawRetriever(client)
content_length = headers.get("content-length")
return ContentLengthRetriever(client, int(content_length))
if not content_length:
logging.warning("Transfer-encoding and content-length not specified, trying without")
return RawRetriever(client)
return ContentLengthRetriever(client, int(content_length))
class PreambleRetriever(Retriever):
"""
Retriever instance for retrieving the start-line and headers of an HTTP message.
"""
client: HTTPSocket
_buffer: []
@@ -59,6 +78,10 @@ class PreambleRetriever(Retriever):
self._buffer = []
def retrieve(self):
"""
Returns an iterator of the retrieved lines.
@return:
"""
line = self.client.read_line()
while True:
@@ -76,6 +99,9 @@ class PreambleRetriever(Retriever):
class ContentLengthRetriever(Retriever):
"""
Retriever instance for retrieving a message body with a given content-length.
"""
length: int
def __init__(self, client: HTTPSocket, length: int):
@@ -83,6 +109,11 @@ class ContentLengthRetriever(Retriever):
self.length = length
def retrieve(self):
"""
Returns an iterator of the received message bytes.
The size of each iteration is not necessarily constant.
@raise IncompleteResponse: if the connection is closed or timed out before receiving the complete payload.
"""
cur_payload_size = 0
read_size = BUFSIZE
@@ -95,10 +126,10 @@ class ContentLengthRetriever(Retriever):
try:
buffer = self.client.read(remaining)
except TimeoutError:
logging.error("Timed out before receiving complete payload")
logging.error("Timed out before receiving the complete payload")
raise IncompleteResponse("Timed out before receiving complete payload")
except ConnectionError:
logging.error("Timed out before receiving complete payload")
logging.error("Connection closed before receiving the complete payload")
raise IncompleteResponse("Connection closed before receiving complete payload")
if len(buffer) == 0:
@@ -110,6 +141,10 @@ class ContentLengthRetriever(Retriever):
class RawRetriever(Retriever):
"""
Retriever instance for retrieve a message body without any length specifier or encoding.
This retriever will keep waiting until a timeout occurs or the connection is disconnected.
"""
def retrieve(self):
while True:
@@ -120,20 +155,28 @@ class RawRetriever(Retriever):
class ChunkedRetriever(Retriever):
"""
Retriever instance for retrieving a message body with chunked encoding.
"""
def retrieve(self):
"""
Returns an iterator of the received message bytes.
The size of each iteration is not necessarily constant.
@raise IncompleteResponse: if the connection is closed or timed out before receiving the complete payload.
"""
while True:
chunk_size = self.__get_chunk_size()
logging.debug("chunk-size: %s", chunk_size)
if chunk_size == 0:
# remove all trailing lines
self.client.reset_request()
break
buffer = self.client.read(chunk_size)
logging.debug("chunk: %r", buffer)
yield buffer
self.client.read_line() # remove CRLF
self.client.read_line() # remove trailing CRLF
def __get_chunk_size(self):
line = self.client.read_line()