如何在FastAPI中提供静态文件服务

22

我正在尝试提供我在“package_docs”目录中的静态文件。当我在浏览器中打开http://127.0.0.1:8001/packages/docs/index.html时,页面可以访问。

但我想打开页面:http://127.0.0.1:8001/packages/docs/,并且不希望看到源文件。然而输出是404 Not Found

app.mount("/packages/docs", 
    StaticFiles(directory=pkg_resources.resource_filename(__name__, 'package_docs')
    ), 
    name="package_docs")

@app.get("/packages/docs/.*", include_in_schema=False)
def root():
    return HTMLResponse(pkg_resources.resource_string(__name__, "package_docs/index.html"))


app.include_router(static.router)
app.include_router(jamcam.router, prefix="/api/v1/cams", tags=["jamcam"])

我该如何修改我的代码?任何建议都将很有帮助。提前致谢。


@justin-malloy发布的答案似乎是正确的,你只需要在StaticFiles()调用中包含html=True。 - Nikhil VJ
请查看这个答案,以及这个详细的答案 - Chris
5个回答

31

在FastAPI中,可以使用Starlette的html选项。 Starlette文档

这将让您拥有类似以下内容:

app.mount("/site", StaticFiles(directory="site", html = True), name="site")

将/site解析为/site/index.html,将/site/foo/解析为/site/foo/index.html等。

如果您想以未使用“directory = /foo”处理的方式更改文件夹名称,则其他答案可以帮助您重定向,但如果您只想加载相关的.html文件,则这是最简单的选项。


谢谢!这应该是被选择的答案,而且 FastAPI 真的应该把这个加入他们的文档中。我曾经在 StaticFiles() 函数的外部使用 html=True,导致出现错误。 - Nikhil VJ

12
你需要使用 FastAPI 的 TemplateResponse(实际上是 Starlette 的):
from fastapi import Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="package_docs")

@app.get("/items/{id}")
async def example(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

请求(Request)在Jinja2上下文中作为键值对的一部分。因此,您还必须将其声明为查询参数。您必须指定您要使用Jinja呈现的HTML文件("your.html", {"request": request})

另外,要直接返回HTMLResponse,您可以使用fastapi.responses中的HTMLResponse

from fastapi.responses import HTMLResponse

@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title></title>
        </head>
        <body>
        </body>
    </html>
    """

您可以阅读有关FastAPI自定义响应的更多信息。


2
这个解决方案将与JavaScript框架(例如Svelte)很好地配合使用,因为您可以像bundle.js?{{bundle_version}}一样使用Jinja渲染index.html,页面将保持最新状态。 - Peko Chan
在最后一行的最后一个例子中,缺少闭合引号 """ - do-me
如果你正在使用前端模板,比如AngularJs,请注意这会导致错误。 - zfj3ub94rf576hc4eegm

3

▶️ 1. 您不需要显式地创建路由来服务/呈现主页/静态文件夹。当您将一个目录标记为静态时,它将自动将第一个参数作为app.mount()中的route,在这种情况下是app.mount("/")。因此,当您输入基础url,例如http://127.0.0.1:8000/,您将获得静态文件。

▶️ 2. FastAPI()类的app实例。是的,在单个FASTAPI应用程序中可以拥有任意数量的实例。

▶️ 3. 其他api相关任务的FastAPI()api_app实例。

之所以需要2和3,是因为如果您只想使用app实例,当用户请求网址http://127.0.0.1:8000/时,用户将获得静态文件,然后当用户请求http://127.0.0.1:8000/hello时,服务器将尝试在static-folder中查找hello,但是在static-folder中没有hello!最终它将是一个not-found类型的响应。这就是为什么需要创建另一个FastAPI实例api_app并注册前缀为/api(▶️ -> 5),因此来自http://127.0.0.1:8000/api/xx的每个请求都会触发装饰器@api_app (▶️ -> 4)!

instance声明和mount()方法调用顺序很重要。

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates

templates = Jinja2Templates(directory='homepage-app')

# for ui
#  -> 2
app = FastAPI(title="homepage-app")

# for api
#  -> 3
api_app = FastAPI(title="api-app")

# for api and route that starts with "/api" and not static
#  -> 5
app.mount("/api", api_app)

# for static files and route that starts with "/"
#  -> 1
app.mount("/", StaticFiles(directory="static-folder", html=True), name="static-folder") 

# for your other api routes you can use `api_app` instance of the FastAPI
#  -> 4
@api_app.get("/hello")
async def say_hello():
    print("hello")
    return {"message": "Hello World"}

3
如果app.mount不是一个选项,您总可以手动读取文件并响应其内容...例如,如果您的静态文件在/site内,则:
from os.path import isfile
from fastapi import Response
from mimetypes import guess_type


@app.get("/site/{filename}")
async def get_site(filename):
    filename = './site/' + filename

    if not isfile(filename):
        return Response(status_code=404)

    with open(filename) as f:
        content = f.read()

    content_type, _ = guess_type(filename)
    return Response(content, media_type=content_type)


@app.get("/site/")
async def get_site_default_filename():
    return await get_site('index.html')

谢谢。我真的需要这个,因为fastapi docker有一个bug。 ModuleNotFoundError: 找不到名为'aiofiles'的模块。 - EminezArtus

1

文档中:

第一个“/static”指的是这个“子应用程序”将被“挂载”的子路径。因此,任何以“/static”开头的路径都将由它处理。

这意味着你在http://127.0.0.1:8001/packages/docs/上挂载了你的目录,但你需要在URL中指定一个文件,或者像你所做的那样添加一个处理程序。问题是,由于你先挂载了路径,所以它不会考虑包含部分路径的后续路径。

一种可能性是首先为http://127.0.0.1:8001/packages/docs/指定路径,以便fastapi处理,然后再挂载文件夹,提供静态文件。

此外,我希望将请求http://127.0.0.1:8001/packages/docs/的用户重定向到http://127.0.0.1:8001/packages/docs/index.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接