Skip to content

Commit

Permalink
Add Chunked Encoding to minimize memory usage for
Browse files Browse the repository at this point in the history
large dynamic html pages.
  • Loading branch information
paul-1 committed Dec 8, 2022
1 parent 5aa7f2f commit d3178de
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
33 changes: 32 additions & 1 deletion adafruit_httpserver/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
cache: int = 0,
headers: Dict[str, str] = None,
body: str = "",
chunked: bool = False,
) -> bytes:
"""Constructs the response bytes from the given parameters."""

Expand All @@ -77,7 +78,10 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
headers = headers or {}

headers.setdefault("Content-Type", content_type)
headers.setdefault("Content-Length", content_length or len(body))
if chunked:
headers.setdefault("Transfer-Encoding", "chunked")
else:
headers.setdefault("Content-Length", content_length or len(body))
headers.setdefault("Connection", "close")

response += f"Cache-Control: max-age={cache}\r\n"
Expand Down Expand Up @@ -120,6 +124,33 @@ def send(self, conn: Union["SocketPool.Socket", "socket.socket"]) -> None:
body=self.body,
)

def send_chunk_headers(
self, conn: Union["SocketPool.Socket", "socket.socket"]
) -> None:
"""Send Headers for a chunked response over the given socket."""
self._send_bytes(
conn,
self._construct_response_bytes(
status=self.status,
content_type=self.content_type,
chunked=True,
cache=self.cache,
body="",
),
)

def send_body_chunk(
self, conn: Union["SocketPool.Socket", "socket.socket"], chunk: str
) -> None:
"""Send chunk of data to the given socket. Send an empty("") chunk to finish the session.
:param Union["SocketPool.Socket", "socket.socket"] conn: Current connection.
:param str chunk: String data to be sent.
"""
size = "%X\r\n".encode() % len(chunk)
self._send_bytes(conn, size)
self._send_bytes(conn, chunk.encode() + b"\r\n")

def _send_response( # pylint: disable=too-many-arguments
self,
conn: Union["SocketPool.Socket", "socket.socket"],
Expand Down
19 changes: 18 additions & 1 deletion adafruit_httpserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ def route_func(request):
print("Received a request of length", len(raw_text), "bytes")
return HTTPResponse(body="hello world")
@server.route(path, method)
def route_func(request, conn):
raw_text = request.raw_request.decode("utf8")
print("Received a request of length", len(raw_text), "bytes")
res = HTTPResponse(content_type="text/html")
res.send_chunk_headers(conn)
res.send_body_chunk(conn, "Some content")
res.send_body_chunk(conn, "Some more content")
res.send_body_chunk(conn, "") # Send empty packet to finish chunked stream
return None # Return None, so server knows that nothing else needs to be sent.
"""

def route_decorator(func: Callable) -> Callable:
Expand Down Expand Up @@ -162,7 +173,13 @@ def poll(self):

# If a handler for route exists and is callable, call it.
if handler is not None and callable(handler):
response = handler(request)
# Need to pass connection for chunked encoding to work.
try:
response = handler(request, conn)
except TypeError:
response = handler(request)
if response is None:
return

# If no handler exists and request method is GET, try to serve a file.
elif request.method == HTTPMethod.GET:
Expand Down

0 comments on commit d3178de

Please sign in to comment.