Skip to content

Commit 4768bbd

Browse files
committed
Add Chunked Encoding to minimize memory usage for
large dynamic html pages.
1 parent e66cfbf commit 4768bbd

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

adafruit_httpserver/response.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
6969
cache: int = 0,
7070
headers: Dict[str, str] = None,
7171
body: str = "",
72+
chunked: bool = False,
7273
) -> bytes:
7374
"""Constructs the response bytes from the given parameters."""
7475

@@ -78,8 +79,11 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
7879
response_headers = {} if headers is None else headers.copy()
7980

8081
response_headers.setdefault("Content-Type", content_type)
81-
response_headers.setdefault("Content-Length", content_length or len(body))
8282
response_headers.setdefault("Connection", "close")
83+
if chunked:
84+
response_headers.setdefault("Transfer-Encoding", "chunked")
85+
else:
86+
response_headers.setdefault("Content-Length", content_length or len(body))
8387

8488
for header, value in response_headers.items():
8589
response += f"{header}: {value}\r\n"
@@ -121,6 +125,33 @@ def send(self, conn: Union["SocketPool.Socket", "socket.socket"]) -> None:
121125
body=self.body,
122126
)
123127

128+
def send_chunk_headers(
129+
self, conn: Union["SocketPool.Socket", "socket.socket"]
130+
) -> None:
131+
"""Send Headers for a chunked response over the given socket."""
132+
self._send_bytes(
133+
conn,
134+
self._construct_response_bytes(
135+
status=self.status,
136+
content_type=self.content_type,
137+
chunked=True,
138+
cache=self.cache,
139+
body="",
140+
),
141+
)
142+
143+
def send_body_chunk(
144+
self, conn: Union["SocketPool.Socket", "socket.socket"], chunk: str
145+
) -> None:
146+
"""Send chunk of data to the given socket. Send an empty("") chunk to finish the session.
147+
148+
:param Union["SocketPool.Socket", "socket.socket"] conn: Current connection.
149+
:param str chunk: String data to be sent.
150+
"""
151+
size = "%X\r\n".encode() % len(chunk)
152+
self._send_bytes(conn, size)
153+
self._send_bytes(conn, chunk.encode() + b"\r\n")
154+
124155
def _send_response( # pylint: disable=too-many-arguments
125156
self,
126157
conn: Union["SocketPool.Socket", "socket.socket"],

adafruit_httpserver/server.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ def route_func(request):
5353
print("Received a request of length", len(raw_text), "bytes")
5454
return HTTPResponse(body="hello world")
5555
56+
57+
@server.route(path, method)
58+
def route_func(request, conn):
59+
raw_text = request.raw_request.decode("utf8")
60+
print("Received a request of length", len(raw_text), "bytes")
61+
res = HTTPResponse(content_type="text/html")
62+
res.send_chunk_headers(conn)
63+
res.send_body_chunk(conn, "Some content")
64+
res.send_body_chunk(conn, "Some more content")
65+
res.send_body_chunk(conn, "") # Send empty packet to finish chunked stream
66+
return None # Return None, so server knows that nothing else needs to be sent.
5667
"""
5768

5869
def route_decorator(func: Callable) -> Callable:
@@ -162,7 +173,13 @@ def poll(self):
162173

163174
# If a handler for route exists and is callable, call it.
164175
if handler is not None and callable(handler):
165-
response = handler(request)
176+
# Need to pass connection for chunked encoding to work.
177+
try:
178+
response = handler(request, conn)
179+
except TypeError:
180+
response = handler(request)
181+
if response is None:
182+
return
166183

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

0 commit comments

Comments
 (0)