Skip to content

Commit d3178de

Browse files
committed
Add Chunked Encoding to minimize memory usage for
large dynamic html pages.
1 parent 5aa7f2f commit d3178de

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

adafruit_httpserver/response.py

Lines changed: 32 additions & 1 deletion
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

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

7980
headers.setdefault("Content-Type", content_type)
80-
headers.setdefault("Content-Length", content_length or len(body))
81+
if chunked:
82+
headers.setdefault("Transfer-Encoding", "chunked")
83+
else:
84+
headers.setdefault("Content-Length", content_length or len(body))
8185
headers.setdefault("Connection", "close")
8286

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

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

adafruit_httpserver/server.py

Lines changed: 18 additions & 1 deletion
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)