Как захватить произвольные пути на одном маршруте в FastAPI?
У меня возникла проблема с маршрутизацией в приложении на React, которое обслуживается через FastAPI. Я монтирую статические файлы следующим образом:
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.route('/session')
async def renderReactApp(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
С этим кодом приложение на React успешно обслуживается, и маршрутизация на клиентской стороне работает корректно. Однако, когда клиент перезагружает страницу по маршруту, который не определён на сервере, но используется в приложении React, FastAPI возвращает ошибку not found
.
Чтобы это исправить, я добавил маршруты в код:
@app.route('/network')
@app.route('/gat')
@app.route('/session')
Но мне кажется, что это неправильно и неэффективно, так как мне нужно добавлять каждый маршрут на сервере, так же, как и на клиенте.
Я уверен, что в FastAPI должно быть что-то подобное, как в Flask:
@flask_app.add_url_rule('/<path:path>', 'index', index)
что позволило бы обрабатывать все произвольные пути. Есть ли аналогичное решение в FastAPI, которое позволило бы мне упростить управление маршрутами?
2 ответ(ов)
Поскольку FastAPI основан на Starlette, вы можете использовать то, что они называют "конвертерами" с параметрами маршрута, используя тип path
в этом случае, который "возвращает оставшуюся часть пути, включая любые дополнительные символы /
."
Смотрите https://www.starlette.io/routing/#path-parameters для справки.
Если ваше приложение на React (или Vue и т. д.) использует базовый путь, вы можете сделать что-то вроде этого, что присвоит все, что находится после /my-app/
, переменной rest_of_path
:
@app.get("/my-app/{rest_of_path:path}")
async def serve_my_app(request: Request, rest_of_path: str):
print("rest_of_path: "+rest_of_path)
return templates.TemplateResponse("index.html", {"request": request})
Если вы не используете уникальный базовый путь, такой как /my-app/
(что, по-видимому, относится к вашему случаю), вы все равно можете этого добиться с помощью универсального маршрута, который должен располагаться после любых других маршрутов, чтобы не перезаписывать их:
@app.route("/{full_path:path}")
async def catch_all(request: Request, full_path: str):
print("full_path: "+full_path)
return templates.TemplateResponse("index.html", {"request": request})
На самом деле, вам стоит использовать этот универсальный маршрут, чтобы различать запросы к /my-app/
и /my-app
.
Простой и эффективный способ совместимости с react-router
Я разработал очень простую функцию, которая полностью совместима с приложениями на react-router
и create-react-app
(в большинстве случаев).
Функция
from pathlib import Path
from typing import Union
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
def serve_react_app(app: FastAPI, build_dir: Union[Path, str]) -> FastAPI:
"""Обслуживает React приложение в корневом каталоге `/`
Args:
app: Экземпляр приложения FastAPI
build_dir: Директория сборки React (созданная с помощью `yarn build` или
`npm run build`)
Returns:
FastAPI: экземпляр с добавленным React приложением
"""
if isinstance(build_dir, str):
build_dir = Path(build_dir)
app.mount(
"/static/",
StaticFiles(directory=build_dir / "static"),
name="Статические файлы React приложения",
)
templates = Jinja2Templates(directory=build_dir.as_posix())
@app.get("/{full_path:path}")
async def serve_react_app(request: Request, full_path: str):
"""Обслуживает React приложение.
Переменная `full_path` необходима для обслуживания каждой возможной конечной точки с
файлом `index.html` для совместимости с `react-router-dom`
"""
return templates.TemplateResponse("index.html", {"request": request})
return app
Использование
import uvicorn
from fastapi import FastAPI
app = FastAPI()
path_to_react_app_build_dir = "./frontend/build"
app = serve_react_app(app, path_to_react_app_build_dir)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
Этот код позволяет легко интегрировать ваше React приложение с FastAPI и обеспечивает полную совместимость с маршрутизацией, что делает его отличным выбором для разработчиков, использующих react-router
.
Как использовать модель Pydantic с данными формы в FastAPI?
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата
Почему statistics.mean() работает так медленно?
Есть ли разница между поднятием экземпляра класса Exception и самого класса Exception?