77from typing import Any , Callable
88
99from reactpy .backend .middleware import ReactPyMiddleware
10+ from reactpy .backend .utils import dict_to_byte_list , find_and_replace
1011from reactpy .core .types import ComponentType
1112
1213_logger = getLogger (__name__ )
@@ -45,11 +46,13 @@ async def standalone_app(
4546 """ASGI app for ReactPy standalone mode."""
4647 if scope ["type" ] != "http" :
4748 if scope ["type" ] != "lifespan" :
48- _logger . warning (
49+ msg = (
4950 "ReactPy app received unsupported request of type '%s' at path '%s'" ,
5051 scope ["type" ],
5152 scope ["path" ],
5253 )
54+ _logger .warning (msg )
55+ raise NotImplementedError (msg )
5356 return
5457
5558 # Store the HTTP response in memory for performance
@@ -59,10 +62,11 @@ async def standalone_app(
5962 # Return headers for all HTTP responses
6063 request_headers = dict (scope ["headers" ])
6164 response_headers : dict [str , str | int ] = {
62- "etag" : f'" { self .etag } "' ,
65+ "etag" : self .etag ,
6366 "last-modified" : self .last_modified ,
6467 "access-control-allow-origin" : "*" ,
6568 "cache-control" : "max-age=60, public" ,
69+ "content-length" : len (self .cached_index_html ),
6670 ** self .extra_headers ,
6771 }
6872
@@ -74,18 +78,19 @@ async def standalone_app(
7478 200 ,
7579 "" ,
7680 content_type = b"text/html" ,
77- headers = self . dict_to_byte_list (response_headers ),
81+ headers = dict_to_byte_list (response_headers ),
7882 )
7983
8084 # Browser already has the content cached
8185 if request_headers .get (b"if-none-match" ) == self .etag .encode ():
86+ response_headers .pop ("content-length" )
8287 return await http_response (
8388 scope ["method" ],
8489 send ,
8590 304 ,
8691 "" ,
8792 content_type = b"text/html" ,
88- headers = self . dict_to_byte_list (response_headers ),
93+ headers = dict_to_byte_list (response_headers ),
8994 )
9095
9196 # Send the index.html
@@ -95,9 +100,7 @@ async def standalone_app(
95100 200 ,
96101 self .cached_index_html ,
97102 content_type = b"text/html" ,
98- headers = self .dict_to_byte_list (
99- response_headers | {"content-length" : len (self .cached_index_html )}
100- ),
103+ headers = dict_to_byte_list (response_headers ),
101104 )
102105
103106 def match_dispatch_path (self , scope : dict ) -> bool :
@@ -109,7 +112,7 @@ async def process_index_html(self):
109112 with open (self .index_html_path , encoding = "utf-8" ) as file_handle :
110113 cached_index_html = file_handle .read ()
111114
112- self .cached_index_html = self . find_and_replace (
115+ self .cached_index_html = find_and_replace (
113116 cached_index_html ,
114117 {
115118 'from "index.ts"' : f'from "{ self .static_path } index.js"' ,
@@ -124,44 +127,11 @@ async def process_index_html(self):
124127 self .etag = hashlib .md5 (
125128 self .cached_index_html .encode (), usedforsecurity = False
126129 ).hexdigest ()
130+ self .etag = f'"{ self .etag } "'
127131
128132 last_modified = os .stat (self .index_html_path ).st_mtime
129133 self .last_modified = formatdate (last_modified , usegmt = True )
130134
131- # @staticmethod
132- # def find_js_filename(content: str) -> str:
133- # """Find the qualified filename of the index.js file."""
134- # substring = 'src="reactpy/static/index-'
135- # location = content.find(substring)
136- # if location == -1:
137- # raise ValueError(f"Could not find {substring} in content")
138- # start = content[location + len(substring) :]
139- # end = start.find('"')
140- # return f"index-{start[:end]}"
141-
142- @staticmethod
143- def dict_to_byte_list (
144- data : dict [str , str | int ],
145- ) -> list [tuple [bytes , bytes ]]:
146- """Convert a dictionary to a list of byte tuples."""
147- result : list [tuple [bytes , bytes ]] = []
148- for key , value in data .items ():
149- new_key = key .encode ()
150- new_value = (
151- value .encode () if isinstance (value , str ) else str (value ).encode ()
152- )
153- result .append ((new_key , new_value ))
154- return result
155-
156- @staticmethod
157- def find_and_replace (content : str , replacements : dict [str , str ]) -> str :
158- """Find and replace content. Throw and error if the substring is not found."""
159- for key , value in replacements .items ():
160- if key not in content :
161- raise ValueError (f"Could not find { key } in content" )
162- content = content .replace (key , value )
163- return content
164-
165135
166136async def http_response (
167137 method : str ,
0 commit comments