英文:
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.html
的FileResponse
/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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论