-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create catch-all/fallback route for UI app #932
Conversation
✅ Deploy Preview for conda-store canceled.
|
9c4bab4
to
61590a0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self review
<script defer src="{{ url_for('get_conda_store_ui') }}static/conda-store-ui/main.js"></script> | ||
<link href="{{ url_for('get_conda_store_ui') }}static/conda-store-ui/main.css" rel="stylesheet"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding url_for('get_conda_store_ui')
was the only way I could figure out how to put the url_prefix here.
Why is this change needed? Previously, the server would only serve the UI app at url_prefix
(e.g., either at "/" or "/conda-store", depending on configuration).
However, this PR allows the UI app to be loaded at /conda-store/default/test, for example. At that URL, this page would try to fetch the JavaScript and CSS at:
- /conda-store/default/static/conda-store-ui/main.js
- /conda-store/default/static/main.css
These routes end up matching the catch-all, which means the server serves this template page for those requests instead of the desired JavaScript and CSS.
conda-store-server/conda_store_server/_internal/server/views/conda_store_ui.py
Outdated
Show resolved
Hide resolved
conda-store-server/conda_store_server/_internal/server/views/conda_store_ui.py
Outdated
Show resolved
Hide resolved
conda-store-server/conda_store_server/_internal/server/views/conda_store_ui.py
Show resolved
Hide resolved
It would be great to turn the manual test plan I put in the PR description into a set of automated tests |
+100 that would be awesome! I would make a new test file in https://github.com/conda-incubator/conda-store/tree/main/conda-store-server/tests/_internal/server/views called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and the routes/html codes are as expected. Thanks!
conda-store-server/conda_store_server/_internal/server/views/conda_store_ui.py
Outdated
Show resolved
Hide resolved
conda-store-server/conda_store_server/_internal/server/views/conda_store_ui.py
Show resolved
Hide resolved
Hmm, looks like something here is making the integration tests fail. |
@router_conda_store_ui.get("/") | ||
@router_conda_store_ui.get("{path:path}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@peytondmurray I'm not entirely sure why, but this seems to be the line that is causing at least some of the CI checks to fail.
If you remove this line and go to localhost:8080/api/v1/namespace, you'll see that it redirects to same URL but with a forward slash at the end. If you then put this line back in and go to the same URL, you'll notice that it tries to load the client app. You get an HTML page back that tries to load the client app at that URL, but the client renders an error since it doesn't recognize that route (api/v1/namespace).
Any idea how to fix it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recap
With line 22 removed:
- localhost:8080/api/v1/namespace -> localhost:8080/api/v1/namespace/
- localhost:8080/api/v1/namespace/ -> JSON containing list of namespaces
With line 22 in place:
- localhost:8080/api/v1/namespace -> index.html for client app
- localhost:8080/api/v1/namespace/ -> JSON containing list of namespaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to suggest to me if FastAPI tries all the routes and cannot find a match, then it will add a trailing slash as a last-ditch attempt to not return a 404.
I see there is an option redirect_slashes
for the FastAPI class whose default value is True.
I will have to look at the FastAPI source code to see how and when it uses that option.
I wonder if the solution is to explicitly map both routes to the same handler. The following code would allow the user to get the internal admin UI via /admin
or via /admin/
@router_ui.get("")
@router_ui.get("/")
async def ui_list_environments(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have context on this, but it seems like this is intended behavior on the FastAPI side of things. Others have wondered about how to prevent this and about the impacts of redirection on http[s].
Looking at the integration tests, the error shows that the URL is malformed:
aiohttp.client_exceptions.ContentTypeError: 200, message='Attempt to decode JSON with unexpected mimetype: text/html; charset=utf-8', url='http://localhost:8080/conda-store/api/v1/environment?status=COMPLETED&artifact=CONDA_PACK&packages=python&packages=ipykernel/'
It seems like this should be http://localhost:8080/conda-store/api/v1/environment/?status=COMPLETED&artifact=CONDA_PACK&packages=python&packages=ipykernel
, no?
@gabalafou @peytondmurray is there any chance this PR will make the next conda-store release? We're trying to upgrade conda-store in Nebari and would like to include this fix if possible. |
@marcelovilla We're in the midst of exploring options here - yesterday I proposed to @gabalafou the idea of simply overriding FastAPI's default HTTPException handler with something like from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.exception_handler(HTTPException):
async def handle_bad_route(request, exc):
return RedirectResponse("/") Will get back to you on whether this can get in the next release! |
@peytondmurray good to know, thanks for the prompt reply! |
I also considered that idea, but I wasn't sure it would work because some of the API endpoints raise a 404 if they cannot find the resource in the database. For example, the /api/v1/namespace/{namespace} endpoint raises 404 if it cannot find the namespace in the database: conda-store/conda-store-server/conda_store_server/_internal/server/views/api.py Lines 282 to 299 in feac99d
My suspicion was that if we add a custom exception handler for 404s to serve the React app, then if someone makes a request to I was imagining something like this: @app.exception_handler(404):
async def get_conda_store_ui(
request: Request,
templates=Depends(dependencies.get_templates),
url_prefix=Depends(dependencies.get_url_prefix),
):
context = {
"request": request,
"url_prefix": url_prefix,
"ui_prefix": router_conda_store_ui.prefix,
}
return templates.TemplateResponse("conda-store-ui.html", context) I created a minimal FastAPI app to double check my suspicion: # main.py
from fastapi import FastAPI, APIRouter, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
app = FastAPI()
router_api = APIRouter(
tags=["api"],
prefix="/api/v1",
)
@router_api.get("/namespace/{namespace}")
async def get_namespace(namespace):
if (namespace == "foobar"):
raise HTTPException(status_code=404, detail="namespace does not exist")
else:
return {"message": f"namespace {namespace} found"}
@app.exception_handler(404)
async def handle_bad_route(request, exc):
html_content = """
<html>
<head>
<title>Conda Store</title>
</head>
<body>
<h1>Pretend I'm a React app</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
app.include_router(router_api) Result:
The only way I see around this is by subclassing or somehow annotating exceptions raised in our code so that we can re-raise them in the exception handler. Out of curiosity, do you have any concerns about the approach I've taken in my most recent commits, which is to put the app at |
No, I don't have a problem with setting the default to
Should we update |
Great, thanks @peytondmurray!
|
Cross-reference: |
Fixes #899.
Pull request checklist
How to test
Before starting, create an environment named "test" under the "default" namespace.
Set
url_prefix
to "/":Set
url_prefix
to "/conda-store", go to the following URLs, behavior should be exactly the same as above: