Skip to content

Commit d53c6f4

Browse files
committed
use asgi_tools for HTTP responses
1 parent 1a221c8 commit d53c6f4

File tree

3 files changed

+12
-69
lines changed

3 files changed

+12
-69
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies = [
3939
"lxml >=4",
4040
"servestatic >=3.0.0",
4141
"orjson >=3",
42+
"asgi-tools",
4243
]
4344
dynamic = ["version"]
4445
urls.Changelog = "https://reactpy.dev/docs/about/changelog.html"

src/reactpy/asgi/standalone.py

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@
88
from logging import getLogger
99
from typing import Callable, Literal, cast, overload
1010

11+
from asgi_tools import ResponseHTML
1112
from asgiref import typing as asgi_types
1213
from typing_extensions import Unpack
1314

1415
from reactpy import html
1516
from reactpy.asgi.middleware import ReactPyMiddleware
16-
from reactpy.asgi.utils import (
17-
dict_to_byte_list,
18-
http_response,
19-
import_dotted_path,
20-
vdom_head_to_html,
21-
)
17+
from reactpy.asgi.utils import import_dotted_path, vdom_head_to_html
2218
from reactpy.types import (
2319
AsgiApp,
2420
AsgiHttpApp,
@@ -40,7 +36,7 @@ def __init__(
4036
self,
4137
root_component: RootComponentConstructor,
4238
*,
43-
http_headers: dict[str, str | int] | None = None,
39+
http_headers: dict[str, str] | None = None,
4440
html_head: VdomDict | None = None,
4541
html_lang: str = "en",
4642
**settings: Unpack[ReactPyConfig],
@@ -182,44 +178,33 @@ async def __call__(
182178

183179
# Response headers for `index.html` responses
184180
request_headers = dict(scope["headers"])
185-
response_headers: dict[str, str | int] = {
181+
response_headers: dict[str, str] = {
186182
"etag": self._etag,
187183
"last-modified": self._last_modified,
188184
"access-control-allow-origin": "*",
189185
"cache-control": "max-age=60, public",
190-
"content-length": len(self._cached_index_html),
186+
"content-length": str(len(self._cached_index_html)),
191187
"content-type": "text/html; charset=utf-8",
192188
**self.parent.extra_headers,
193189
}
194190

195191
# Browser is asking for the headers
196192
if scope["method"] == "HEAD":
197-
return await http_response(
198-
send=send,
199-
method=scope["method"],
200-
headers=dict_to_byte_list(response_headers),
201-
)
193+
response = ResponseHTML("", headers=response_headers)
194+
return await response(scope, receive, send) # type: ignore
202195

203196
# Browser already has the content cached
204197
if (
205198
request_headers.get(b"if-none-match") == self._etag.encode()
206199
or request_headers.get(b"if-modified-since") == self._last_modified.encode()
207200
):
208201
response_headers.pop("content-length")
209-
return await http_response(
210-
send=send,
211-
method=scope["method"],
212-
code=304,
213-
headers=dict_to_byte_list(response_headers),
214-
)
202+
response = ResponseHTML("", headers=response_headers, status_code=304)
203+
return await response(scope, receive, send) # type: ignore
215204

216205
# Send the index.html
217-
await http_response(
218-
send=send,
219-
method=scope["method"],
220-
message=self._cached_index_html,
221-
headers=dict_to_byte_list(response_headers),
222-
)
206+
response = ResponseHTML(self._cached_index_html, headers=response_headers)
207+
await response(scope, receive, send) # type: ignore
223208

224209
def process_index_html(self) -> None:
225210
"""Process the index.html and store the results in memory."""

src/reactpy/asgi/utils.py

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from importlib import import_module
66
from typing import Any
77

8-
from asgiref import typing as asgi_types
9-
108
from reactpy._option import Option
119
from reactpy.types import ReactPyConfig, VdomDict
1210
from reactpy.utils import vdom_to_html
@@ -55,18 +53,6 @@ def check_path(url_path: str) -> str: # pragma: no cover
5553
return ""
5654

5755

58-
def dict_to_byte_list(
59-
data: dict[str, str | int],
60-
) -> list[tuple[bytes, bytes]]:
61-
"""Convert a dictionary to a list of byte tuples."""
62-
result: list[tuple[bytes, bytes]] = []
63-
for key, value in data.items():
64-
new_key = key.encode()
65-
new_value = value.encode() if isinstance(value, str) else str(value).encode()
66-
result.append((new_key, new_value))
67-
return result
68-
69-
7056
def vdom_head_to_html(head: VdomDict) -> str:
7157
if isinstance(head, dict) and head.get("tagName") == "head":
7258
return vdom_to_html(head)
@@ -76,35 +62,6 @@ def vdom_head_to_html(head: VdomDict) -> str:
7662
)
7763

7864

79-
async def http_response(
80-
*,
81-
send: asgi_types.ASGISendCallable,
82-
method: str,
83-
code: int = 200,
84-
message: str = "",
85-
headers: Iterable[tuple[bytes, bytes]] = (),
86-
) -> None:
87-
"""Sends a HTTP response using the ASGI `send` API."""
88-
start_msg: asgi_types.HTTPResponseStartEvent = {
89-
"type": "http.response.start",
90-
"status": code,
91-
"headers": [*headers],
92-
"trailers": False,
93-
}
94-
body_msg: asgi_types.HTTPResponseBodyEvent = {
95-
"type": "http.response.body",
96-
"body": b"",
97-
"more_body": False,
98-
}
99-
100-
# Add the content type and body to everything other than a HEAD request
101-
if method != "HEAD":
102-
body_msg["body"] = message.encode()
103-
104-
await send(start_msg)
105-
await send(body_msg)
106-
107-
10865
def process_settings(settings: ReactPyConfig) -> None:
10966
"""Process the settings and return the final configuration."""
11067
from reactpy import config

0 commit comments

Comments
 (0)