FastAPI – 放在根路由挂载后的通配路由不会被匹配到

huangapple go评论82阅读模式
英文:

FastAPI - catch-all route put after root route mount doesn't get hit

问题

我正在尝试为React SPA和一些API端点提供服务,使用FastAPI。React有自己的路由,为了分担责任,我使用了2个FastAPI应用程序 - 一个带有所有授权功能,并挂载到API路由的应用程序,第二个只有一个任务 - 所有不以"/api"开头的请求应该返回SPA的"index.html"。听起来很简单,对吧?嗯,要么我漏掉了一些非常基本的东西,要么事情并不那么简单。

所以这是API应用程序的挂载(这里没有问题,运行正常):

api_app = secure_authenticated_app()  # 返回一个调整过的FastAPI应用程序

# 主应用程序
app = FastAPI()  

app.mount("/api", api_app, name="api")

然后,派对开始了。

client_folder_path = pathlib.Path(__file__).parent.absolute().joinpath("build")

app.mount("", StaticFiles(directory=client_folder_path, html=True), name="client")


@app.get("/{full_path:path}")
async def serve_client():
    with open(client_folder_path.joinpath("index.html")) as fh:
        data = fh.read()
    return Response(content=data, media_type="text/html")

我的逻辑是:将客户端构建文件夹挂载到根路径,以便它可以轻松找到所有的资产,在到达根路径时提供"index.html",如果遇到一些你不知道的路径 - 别担心,只是提供"index.html"。

实际上它会从根路径提供html,其他前端资源也会被提供,但我一直看到作为"通配符路径"的星星解决方案的/{full_path:path},并没有被命中 - 类似/whatever的路由会返回404。

我尝试过移动它们 - 没有运气,每次其中一个功能(要么从根路径提供html,要么从任何其他路径提供html,或者两者兼而有之)都不起作用。对于来自Node的这个行为来说,这真的很令人惊讶;有没有简单漂亮的解决方案,而不必编写完整的帮助类?

英文:

I'm trying to serve React SPA and a few API endpoints from FastAPI. React has its own routing, so in order to split responsibilities I use 2 FastAPI apps - one comes with all authorization bells and whistles and is mounted to the API route, and the second has one job - all requests that don't begin with /api should return the SPA's index.html. Sounds simple, right? Well, I either miss something really basic, or it's not that simple after all.

So this is the API app mounting (no questions here, works fine):

api_app = secure_authenticated_app()  # returns a tweaked FastAPI app

# main app
app = FastAPI()  

app.mount("/api", api_app, name="api")

But then, the party starts.

client_folder_path = pathlib.Path(__file__).parent.absolute().joinpath("build")

app.mount("", StaticFiles(directory=client_folder_path, html=True), name="client")


@app.get("/{full_path:path}")
async def serve_client():
    with open(client_folder_path.joinpath("index.html")) as fh:
        data = fh.read()
    return Response(content=data, media_type="text/html")

My logic here following is: mount the client build folder to the root path so it could easily find all its assets, serve the index.html when getting to the root path, and if you encounter some route that you don't know - no worries, just serve index.html.

In reality it serves the html from the root path, other frontend assets are served too, but the /{full_path:path}, which I keep seeing as a starlette solution for 'wildcard route', doesn't get hit - routes like /whtever return 404.

I tried moving them around - with no luck, each time one of the features (either serving html from root, serving it from any other path or both) won't work. For the one coming from Node, this behavior is really surprising; is there a simple beautiful solution without writing full-blown helper classes?

答案1

得分: 1

正如这个回答中解释的那样,以及这里这里,端点的定义顺序很重要,因为在FastAPI中,端点的评估顺序很重要。请参阅这些引用以获取更多详细信息。

因此,尽管将api_app挂载到/api,然后将StaticFiles实例/app挂载到/(即根路径)会正常工作,在挂载StaticFiles应用程序到/之后添加捕获任意路径的端点将不起作用。原因很简单,因为该端点永远不会被访问/调用,任何以/路径开头的请求(例如,/whatever)都将由StaticFiles应用程序处理—"挂载"意味着将一个完整的"独立"应用程序添加到特定路径上,然后负责处理所有子路径。

因此,如果您仍希望将StaticFiles应用程序挂载到/(即根路径),解决方案是首先挂载api_app,然后是StaticFiles应用程序,并使用自定义异常处理程序,正如这个答案中所示,以处理404 Not Found异常/错误并返回您的自定义响应—在您的情况下,返回index.htmlFileResponse/TemplateResponse。关于自定义异常处理的相关答案可能对您有帮助,可以在这里以及这里这里找到。

英文:

As explained in this answer, as well as here and here, the order in which the endpoints are defined matters, as, in FastAPI, endpoints are evaluated in order. Please have a look at those references for more details.

Hence, while mounting api_app to /api, and then the StaticFiles instance/app to / (i.e., root path) would work fine, adding an endpoint that captures arbitrary paths, after mounting the StaticFiles app to /, would not work. The reason is simply bacause that endpoint would never be reached/called, as any request that starts with / path (e.g., /whatever) would be handled by the StaticFiles app—"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.

Thus, if you still would like having the StaticFiles app mounted to / (i.e., root path), a solution would be to have the api_app mounted first, followed by the StaticFiles app, and use a custom exception handler, as demonstrated in this answer, in order to handle 404 Not Found exceptions/errors and return your custom response—in your case, return a FileResponse/TemplateResponse of index.html. Related answers about custom exception handling that might prove helpful to you can be found here, as well as here and here.

huangapple
  • 本文由 发表于 2023年6月22日 05:44:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76527355.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定