0

Как захватить произвольные пути на одном маршруте в FastAPI?

16

У меня возникла проблема с маршрутизацией в приложении на 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 ответ(ов)

0

Поскольку 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.

0

Простой и эффективный способ совместимости с 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.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь