-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(standalone): fix api path when embedding front-end (#2136)
Distinguish root_path and api_prefix. root_path is to be used when a proxy prepends a prefix for the client, api_prefix is to be used when we want our server itself to prepend a prefix. Signed-off-by: Sylvain Leclerc <[email protected]>
- Loading branch information
Showing
39 changed files
with
909 additions
and
851 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Copyright (c) 2024, RTE (https://www.rte-france.com) | ||
# | ||
# See AUTHORS.txt | ||
# | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
# | ||
# SPDX-License-Identifier: MPL-2.0 | ||
# | ||
# This file is part of the Antares project. | ||
|
||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
from fastapi import APIRouter, FastAPI | ||
|
||
|
||
@dataclass(frozen=True) | ||
class AppBuildContext: | ||
""" | ||
Base elements of the application, for use at construction time: | ||
- app: the actual fastapi application, where middlewares, exception handlers, etc. may be added | ||
- api_root: the route under which all API and WS endpoints must be registered | ||
API routes should not be added straight to app, but under api_root instead, | ||
so that they are correctly prefixed if needed (/api for standalone mode). | ||
Warning: the inclusion of api_root must happen AFTER all subroutes | ||
have been registered, hence the build method. | ||
""" | ||
|
||
app: FastAPI | ||
api_root: APIRouter | ||
|
||
def build(self) -> FastAPI: | ||
""" | ||
Finalizes the app construction by including the API route. | ||
Must be performed AFTER all subroutes have been added. | ||
""" | ||
self.app.include_router(self.api_root) | ||
return self.app | ||
|
||
|
||
def create_app_ctxt(app: FastAPI, api_root: Optional[APIRouter] = None) -> AppBuildContext: | ||
if not api_root: | ||
api_root = APIRouter() | ||
return AppBuildContext(app, api_root) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# Copyright (c) 2024, RTE (https://www.rte-france.com) | ||
# | ||
# See AUTHORS.txt | ||
# | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
# | ||
# SPDX-License-Identifier: MPL-2.0 | ||
# | ||
# This file is part of the Antares project. | ||
""" | ||
This module contains the logic necessary to serve both | ||
the front-end application and the backend HTTP application. | ||
This includes: | ||
- serving static frontend files | ||
- redirecting "not found" requests to home, which itself redirects to index.html | ||
- providing the endpoint /config.json, which the front-end uses to know | ||
what are the API and websocket prefixes | ||
""" | ||
|
||
import re | ||
from pathlib import Path | ||
from typing import Any, Optional, Sequence | ||
|
||
from fastapi import FastAPI | ||
from pydantic import BaseModel | ||
from starlette.middleware.base import BaseHTTPMiddleware, DispatchFunction, RequestResponseEndpoint | ||
from starlette.requests import Request | ||
from starlette.responses import FileResponse | ||
from starlette.staticfiles import StaticFiles | ||
from starlette.types import ASGIApp | ||
|
||
from antarest.core.utils.string import to_camel_case | ||
|
||
|
||
class RedirectMiddleware(BaseHTTPMiddleware): | ||
""" | ||
Middleware that rewrites the URL path to "/" for incoming requests | ||
that do not match the known end points. This is useful for redirecting requests | ||
to the main page of a ReactJS application when the user refreshes the browser. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
app: ASGIApp, | ||
dispatch: Optional[DispatchFunction] = None, | ||
route_paths: Sequence[str] = (), | ||
) -> None: | ||
""" | ||
Initializes an instance of the URLRewriterMiddleware. | ||
Args: | ||
app: The ASGI application to which the middleware is applied. | ||
dispatch: The dispatch function to use. | ||
route_paths: The known route paths of the application. | ||
Requests that do not match any of these paths will be rewritten to the root path. | ||
Note: | ||
The `route_paths` should contain all the known endpoints of the application. | ||
""" | ||
dispatch = self.dispatch if dispatch is None else dispatch | ||
super().__init__(app, dispatch) | ||
self.known_prefixes = {re.findall(r"/(?:(?!/).)*", p)[0] for p in route_paths if p != "/"} | ||
|
||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Any: | ||
""" | ||
Intercepts the incoming request and rewrites the URL path if necessary. | ||
Passes the modified or original request to the next middleware or endpoint handler. | ||
""" | ||
url_path = request.scope["path"] | ||
if url_path in {"", "/"}: | ||
pass | ||
elif not any(url_path.startswith(ep) for ep in self.known_prefixes): | ||
request.scope["path"] = "/" | ||
return await call_next(request) | ||
|
||
|
||
class BackEndConfig(BaseModel): | ||
""" | ||
Configuration about backend URLs served to the frontend. | ||
""" | ||
|
||
rest_endpoint: str | ||
ws_endpoint: str | ||
|
||
class Config: | ||
populate_by_name = True | ||
alias_generator = to_camel_case | ||
|
||
|
||
def create_backend_config(api_prefix: str) -> BackEndConfig: | ||
if not api_prefix.startswith("/"): | ||
api_prefix = "/" + api_prefix | ||
return BackEndConfig(rest_endpoint=f"{api_prefix}", ws_endpoint=f"{api_prefix}/ws") | ||
|
||
|
||
def add_front_app(application: FastAPI, resources_dir: Path, api_prefix: str) -> None: | ||
""" | ||
This functions adds the logic necessary to serve both | ||
the front-end application and the backend HTTP application. | ||
This includes: | ||
- serving static frontend files | ||
- redirecting "not found" requests to home, which itself redirects to index.html | ||
- providing the endpoint /config.json, which the front-end uses to know | ||
what are the API and websocket prefixes | ||
""" | ||
backend_config = create_backend_config(api_prefix) | ||
|
||
front_app_dir = resources_dir / "webapp" | ||
|
||
# Serve front-end files | ||
application.mount( | ||
"/static", | ||
StaticFiles(directory=front_app_dir), | ||
name="static", | ||
) | ||
|
||
# Redirect home to index.html | ||
@application.get("/", include_in_schema=False) | ||
def home(request: Request) -> Any: | ||
return FileResponse(front_app_dir / "index.html", 200) | ||
|
||
# Serve config for the front-end at /config.json | ||
@application.get("/config.json", include_in_schema=False) | ||
def get_api_paths_config(request: Request) -> BackEndConfig: | ||
return backend_config | ||
|
||
# When the web application is running in Desktop mode, the ReactJS web app | ||
# is served at the `/static` entry point. Any requests that are not API | ||
# requests should be redirected to the `index.html` file, which will handle | ||
# the route provided by the URL. | ||
route_paths = [r.path for r in application.routes] # type: ignore | ||
application.add_middleware( | ||
RedirectMiddleware, | ||
route_paths=route_paths, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.