Skip to content

Commit 459d3bc

Browse files
committed
more refactoring to standalone mode
1 parent 3480475 commit 459d3bc

File tree

2 files changed

+42
-52
lines changed

2 files changed

+42
-52
lines changed

src/reactpy/backend/standalone.py

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import hashlib
22
import os
33
import re
4-
from collections.abc import Coroutine, Sequence
4+
from collections.abc import Coroutine
55
from dataclasses import dataclass
66
from email.utils import formatdate
77
from logging import getLogger
@@ -10,7 +10,12 @@
1010

1111
from reactpy import html
1212
from reactpy.backend.middleware import ReactPyMiddleware
13-
from reactpy.backend.utils import dict_to_byte_list, replace_many, vdom_head_to_html
13+
from reactpy.backend.utils import (
14+
dict_to_byte_list,
15+
http_response,
16+
replace_many,
17+
vdom_head_to_html,
18+
)
1419
from reactpy.core.types import VdomDict
1520
from reactpy.types import RootComponentConstructor
1621

@@ -45,6 +50,9 @@ def __init__(
4550

4651
@dataclass
4752
class ReactPyApp:
53+
"""ASGI app for ReactPy's standalone mode. This is utilized by `ReactPyMiddleware` as an alternative
54+
to a user provided ASGI app."""
55+
4856
parent: ReactPy
4957
_cached_index_html = ""
5058
_etag = ""
@@ -58,7 +66,6 @@ async def __call__(
5866
receive: Callable[..., Coroutine],
5967
send: Callable[..., Coroutine],
6068
) -> None:
61-
"""ASGI app for ReactPy standalone mode."""
6269
if scope["type"] != "http":
6370
if scope["type"] != "lifespan":
6471
msg = (
@@ -74,47 +81,41 @@ async def __call__(
7481
if not self._cached_index_html:
7582
self.process_index_html()
7683

77-
# Return headers for all HTTP responses
84+
# Response headers for `index.html` responses
7885
request_headers = dict(scope["headers"])
7986
response_headers: dict[str, str | int] = {
8087
"etag": self._etag,
8188
"last-modified": self._last_modified,
8289
"access-control-allow-origin": "*",
8390
"cache-control": "max-age=60, public",
8491
"content-length": len(self._cached_index_html),
92+
"content-type": "text/html; charset=utf-8",
8593
**self.parent.extra_headers,
8694
}
8795

8896
# Browser is asking for the headers
8997
if scope["method"] == "HEAD":
9098
return await http_response(
91-
scope["method"],
92-
send,
93-
200,
94-
"",
95-
content_type=b"text/html",
99+
send=send,
100+
method=scope["method"],
96101
headers=dict_to_byte_list(response_headers),
97102
)
98103

99104
# Browser already has the content cached
100105
if request_headers.get(b"if-none-match") == self._etag.encode():
101106
response_headers.pop("content-length")
102107
return await http_response(
103-
scope["method"],
104-
send,
105-
304,
106-
"",
107-
content_type=b"text/html",
108+
send=send,
109+
method=scope["method"],
110+
code=304,
108111
headers=dict_to_byte_list(response_headers),
109112
)
110113

111114
# Send the index.html
112115
await http_response(
113-
scope["method"],
114-
send,
115-
200,
116-
self._cached_index_html,
117-
content_type=b"text/html",
116+
send=send,
117+
method=scope["method"],
118+
message=self._cached_index_html,
118119
headers=dict_to_byte_list(response_headers),
119120
)
120121

@@ -145,33 +146,3 @@ def process_index_html(self):
145146
self._last_modified = formatdate(
146147
os.stat(self._index_html_path).st_mtime, usegmt=True
147148
)
148-
149-
150-
async def http_response(
151-
method: str,
152-
send: Callable[..., Coroutine],
153-
code: int,
154-
message: str,
155-
content_type: bytes = b"text/plain",
156-
headers: Sequence = (),
157-
) -> None:
158-
"""Sends a HTTP response using the ASGI `send` API."""
159-
# Head requests don't need body content
160-
if method == "HEAD":
161-
await send(
162-
{
163-
"type": "http.response.start",
164-
"status": code,
165-
"headers": [*headers],
166-
}
167-
)
168-
await send({"type": "http.response.body"})
169-
else:
170-
await send(
171-
{
172-
"type": "http.response.start",
173-
"status": code,
174-
"headers": [(b"content-type", content_type), *headers],
175-
}
176-
)
177-
await send({"type": "http.response.body", "body": message.encode()})

src/reactpy/backend/utils.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from __future__ import annotations
22

33
import logging
4-
import re
5-
from collections.abc import Iterable
4+
from collections.abc import Coroutine, Iterable, Sequence
65
from importlib import import_module
7-
from typing import Any
6+
from typing import Any, Callable
87

98
from reactpy.core.types import ComponentType, VdomDict
109
from reactpy.utils import vdom_to_html
@@ -86,3 +85,23 @@ def vdom_head_to_html(head: VdomDict) -> str:
8685
raise ValueError(
8786
"Invalid head element! Element must be either `html.head` or a string."
8887
)
88+
89+
90+
async def http_response(
91+
*,
92+
send: Callable[[dict[str, Any]], Coroutine],
93+
method: str,
94+
code: int = 200,
95+
message: str = "",
96+
headers: Sequence = (),
97+
) -> None:
98+
"""Sends a HTTP response using the ASGI `send` API."""
99+
start_msg = {"type": "http.response.start", "status": code, "headers": [*headers]}
100+
body_msg: dict[str, str | bytes] = {"type": "http.response.body"}
101+
102+
# Add the content type and body to everything other than a HEAD request
103+
if method != "HEAD":
104+
body_msg["body"] = message.encode()
105+
106+
await send(start_msg)
107+
await send(body_msg)

0 commit comments

Comments
 (0)