11import hashlib
22import os
33import re
4- from collections .abc import Coroutine , Sequence
4+ from collections .abc import Coroutine
55from dataclasses import dataclass
66from email .utils import formatdate
77from logging import getLogger
1010
1111from reactpy import html
1212from 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+ )
1419from reactpy .core .types import VdomDict
1520from reactpy .types import RootComponentConstructor
1621
@@ -45,6 +50,9 @@ def __init__(
4550
4651@dataclass
4752class 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 ()})
0 commit comments