From 674ead1675e434882a2ea66676a74c89ae747c56 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Fri, 28 Jun 2024 11:25:12 +0200 Subject: [PATCH] fix: support static files on pyodide / py.cafe under a prefix When a shiny app is running under a prefix (root_path in ASGI terms), the static files are not served correctly under pyodide. This is because the ASGI path includes the root_path, and the root_path should be removed. Starlette 0.33 and 0.34 however, did not set root_path correctly, and in those cases, we have to rely on route_path. Related discussions: https://github.com/encode/starlette/pull/2400 https://github.com/encode/starlette/discussions/2361 A related fix we had in Solara: https://github.com/widgetti/solara/pull/413/ But this fix also did not seem to work for our situation at https://py.cafe I logged the output of the relevant entries in the scope dict, together with the starlette version: ``` starlette 0.32.0 {'path': '/strftime-min.js', 'root_path': '/_app/lib/strftime-0.9.2'} starlette 0.33.0 {'path': '/_app/lib/strftime-0.9.2/strftime-min.js', 'root_path': '', 'route_root_path': '/lib/strftime-0.9.2', 'route_path': '/strftime-min.js'} starlette 0.34.0 {'path': '/_app/lib/strftime-0.9.2/strftime-min.js', 'root_path': '', 'route_root_path': '/lib/strftime-0.9.2', 'route_path': '/strftime-min.js'} starlette 0.35.0 {'path': '/_app/lib/strftime-0.9.2/strftime-min.js', 'root_path': '/_app/lib/strftime-0.9.2'} starlette 0.36.0 {'path': '/_app/lib/strftime-0.9.2/strftime-min.js', 'root_path': '/_app/lib/strftime-0.9.2'} starlette 0.37.2 {'path': '/_app/lib/strftime-0.9.2/strftime-min.js', 'root_path': '/_app/lib/strftime-0.9.2'} ``` This was using a similar situation as on py.cafe: ```python .... routes = [ Mount('/static', app=app_static), Mount('/_app', app=app_shiny) ] app = Starlette(routes=routes) ``` Which led me to the following fix, making shiny work under pyodide in combination with a prefix with the above mentioned versions of starlette. --- shiny/http_staticfiles.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shiny/http_staticfiles.py b/shiny/http_staticfiles.py index db38f543c..9caed7563 100644 --- a/shiny/http_staticfiles.py +++ b/shiny/http_staticfiles.py @@ -11,6 +11,8 @@ from __future__ import annotations +import re + __all__ = ( "StaticFiles", "FileResponse", @@ -53,7 +55,9 @@ def __init__(self, *, directory: str | os.PathLike[str]): async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http": raise AssertionError("StaticFiles can't handle non-http request") - path = scope["path"] + # following starlette >=0.33, tested to be compatible with 0.32-0.37.2 + root_path = scope.get("route_root_path", scope.get("root_path", "")) + path = scope.get("route_path", re.sub(r"^" + root_path, "", scope["path"])) path_segments = path.split("/") final_path, trailing_slash = _traverse_url_path(self.dir, path_segments) if final_path is None: