|
| 1 | +import asyncio |
1 | 2 | import logging |
2 | 3 | import mimetypes |
3 | 4 | import os |
4 | 5 | import re |
| 6 | +import urllib.parse |
| 7 | +from collections.abc import Sequence |
5 | 8 | from pathlib import Path |
6 | | -from typing import Sequence |
7 | 9 |
|
8 | 10 | import aiofiles |
| 11 | +import orjson |
9 | 12 | from asgiref.compatibility import guarantee_single_callable |
10 | 13 |
|
11 | 14 | from reactpy.backend._common import ( |
12 | 15 | CLIENT_BUILD_DIR, |
13 | 16 | traversal_safe_path, |
14 | 17 | vdom_head_elements_to_html, |
15 | 18 | ) |
| 19 | +from reactpy.backend.hooks import ConnectionContext |
16 | 20 | from reactpy.backend.mimetypes import DEFAULT_MIME_TYPES |
| 21 | +from reactpy.backend.types import Connection, Location |
17 | 22 | from reactpy.config import REACTPY_WEB_MODULES_DIR |
| 23 | +from reactpy.core.layout import Layout |
| 24 | +from reactpy.core.serve import serve_layout |
18 | 25 | from reactpy.core.types import VdomDict |
19 | 26 |
|
20 | 27 | DEFAULT_STATIC_PATH = f"{os.getcwd()}/static" |
@@ -89,6 +96,43 @@ async def __call__(self, scope, receive, send) -> None: |
89 | 96 | async def component_dispatch_app(self, scope, receive, send) -> None: |
90 | 97 | """The ASGI application for ReactPy Python components.""" |
91 | 98 |
|
| 99 | + self._reactpy_recv_queue: asyncio.Queue = asyncio.Queue() |
| 100 | + parsed_url = urllib.parse.urlparse(scope["path"]) |
| 101 | + |
| 102 | + # TODO: Get the component via URL attached to template tag |
| 103 | + parsed_url_query = urllib.parse.parse_qs(parsed_url.query) |
| 104 | + component = lambda _: None |
| 105 | + |
| 106 | + while True: |
| 107 | + event = await receive() |
| 108 | + |
| 109 | + if event["type"] == "websocket.connect": |
| 110 | + await send({"type": "websocket.accept"}) |
| 111 | + |
| 112 | + await serve_layout( |
| 113 | + Layout( |
| 114 | + ConnectionContext( |
| 115 | + component(), |
| 116 | + value=Connection( |
| 117 | + scope=scope, |
| 118 | + location=Location( |
| 119 | + parsed_url.path, |
| 120 | + f"?{parsed_url.query}" if parsed_url.query else "", |
| 121 | + ), |
| 122 | + carrier=self, |
| 123 | + ), |
| 124 | + ) |
| 125 | + ), |
| 126 | + send_json(send), |
| 127 | + self._reactpy_recv_queue.get, |
| 128 | + ) |
| 129 | + |
| 130 | + if event["type"] == "websocket.disconnect": |
| 131 | + break |
| 132 | + |
| 133 | + if event["type"] == "websocket.receive": |
| 134 | + await self._reactpy_recv_queue.put(orjson.loads(event["text"])) |
| 135 | + |
92 | 136 | async def js_modules_app(self, scope, receive, send) -> None: |
93 | 137 | """The ASGI application for ReactPy web modules.""" |
94 | 138 |
|
@@ -166,6 +210,15 @@ async def index_html_app(self, scope, receive, send) -> None: |
166 | 210 | ) |
167 | 211 |
|
168 | 212 |
|
| 213 | +def send_json(send) -> None: |
| 214 | + """Use orjson to send JSON over an ASGI websocket.""" |
| 215 | + |
| 216 | + async def _send_json(value) -> None: |
| 217 | + await send({"type": "websocket.send", "text": orjson.dumps(value)}) |
| 218 | + |
| 219 | + return _send_json |
| 220 | + |
| 221 | + |
169 | 222 | async def simple_response( |
170 | 223 | send, |
171 | 224 | code: int, |
|
0 commit comments