Skip to content

Commit c1d2f55

Browse files
committed
Merge remote-tracking branch 'origin/main' into test
2 parents aeb95a2 + 5de66fa commit c1d2f55

File tree

2 files changed

+68
-8
lines changed

2 files changed

+68
-8
lines changed

adafruit_httpserver/response.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class HTTPResponse:
3030
status: HTTPStatus
3131
headers: HTTPHeaders
3232
content_type: str
33-
33+
cache: Optional[int]
3434
filename: Optional[str]
3535
root_path: str
3636

@@ -42,6 +42,7 @@ def __init__( # pylint: disable=too-many-arguments
4242
body: str = "",
4343
headers: Union[HTTPHeaders, Dict[str, str]] = None,
4444
content_type: str = MIMEType.TYPE_TXT,
45+
cache: Optional[int] = 0,
4546
filename: Optional[str] = None,
4647
root_path: str = "",
4748
http_version: str = "HTTP/1.1",
@@ -57,6 +58,7 @@ def __init__( # pylint: disable=too-many-arguments
5758
headers.copy() if isinstance(headers, HTTPHeaders) else HTTPHeaders(headers)
5859
)
5960
self.content_type = content_type
61+
self.cache = cache
6062
self.filename = filename
6163
self.root_path = root_path
6264
self.http_version = http_version
@@ -67,8 +69,10 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
6769
status: HTTPStatus = CommonHTTPStatus.OK_200,
6870
content_type: str = MIMEType.TYPE_TXT,
6971
content_length: Union[int, None] = None,
70-
headers: HTTPHeaders = None,
72+
cache: int = 0,
73+
headers: Dict[str, str] = None,
7174
body: str = "",
75+
chunked: bool = False,
7276
) -> bytes:
7377
"""Constructs the response bytes from the given parameters."""
7478

@@ -81,11 +85,21 @@ def _construct_response_bytes( # pylint: disable=too-many-arguments
8185
)
8286
headers.setdefault("Connection", "close")
8387

84-
for header, value in headers.items():
85-
response_message_header += f"{header}: {value}\r\n"
86-
response_message_header += "\r\n"
88+
response_headers.setdefault("Content-Type", content_type)
89+
response_headers.setdefault("Connection", "close")
90+
if chunked:
91+
response_headers.setdefault("Transfer-Encoding", "chunked")
92+
else:
93+
response_headers.setdefault("Content-Length", content_length or len(body))
94+
95+
for header, value in response_headers.items():
96+
response += f"{header}: {value}\r\n"
97+
98+
response += f"Cache-Control: max-age={cache}\r\n"
8799

88-
return response_message_header.encode("utf-8") + encoded_response_message_body
100+
response += f"\r\n{body}"
101+
102+
return response.encode("utf-8")
89103

90104
def send(self, conn: Union["SocketPool.Socket", "socket.socket"]) -> None:
91105
"""
@@ -118,6 +132,33 @@ def send(self, conn: Union["SocketPool.Socket", "socket.socket"]) -> None:
118132
body=self.body,
119133
)
120134

135+
def send_chunk_headers(
136+
self, conn: Union["SocketPool.Socket", "socket.socket"]
137+
) -> None:
138+
"""Send Headers for a chunked response over the given socket."""
139+
self._send_bytes(
140+
conn,
141+
self._construct_response_bytes(
142+
status=self.status,
143+
content_type=self.content_type,
144+
chunked=True,
145+
cache=self.cache,
146+
body="",
147+
),
148+
)
149+
150+
def send_body_chunk(
151+
self, conn: Union["SocketPool.Socket", "socket.socket"], chunk: str
152+
) -> None:
153+
"""Send chunk of data to the given socket. Send an empty("") chunk to finish the session.
154+
155+
:param Union["SocketPool.Socket", "socket.socket"] conn: Current connection.
156+
:param str chunk: String data to be sent.
157+
"""
158+
size = "%X\r\n".encode() % len(chunk)
159+
self._send_bytes(conn, size)
160+
self._send_bytes(conn, chunk.encode() + b"\r\n")
161+
121162
def _send_response( # pylint: disable=too-many-arguments
122163
self,
123164
conn: Union["SocketPool.Socket", "socket.socket"],
@@ -131,6 +172,7 @@ def _send_response( # pylint: disable=too-many-arguments
131172
self._construct_response_bytes(
132173
status=status,
133174
content_type=content_type,
175+
cache=self.cache,
134176
headers=headers,
135177
body=body,
136178
),
@@ -150,6 +192,7 @@ def _send_file_response( # pylint: disable=too-many-arguments
150192
status=self.status,
151193
content_type=MIMEType.from_file_name(filename),
152194
content_length=file_length,
195+
cache=self.cache,
153196
headers=headers,
154197
),
155198
)

adafruit_httpserver/server.py

Lines changed: 19 additions & 2 deletions
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,12 +173,18 @@ 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:
169186
response = HTTPResponse(
170-
filename=request.path, root_path=self.root_path
187+
filename=request.path, root_path=self.root_path, cache=604800
171188
)
172189

173190
# If no handler exists and request method is not GET, return 400 Bad Request.

0 commit comments

Comments
 (0)