FastAPI 静态文件服务详解
FastAPI 不仅能构建 API 接口,还内置了对静态文件服务的支持。通过 StaticFiles,你可以轻松地将图片、CSS、JavaScript 或任意静态资源目录挂载到应用中,像传统 Web 服务器一样对外提供文件访问。
为什么需要静态文件服务
大多数 Web 应用除了 API 接口外,还需要直接向浏览器提供静态资源。比如:
- 前端打包后的 HTML、CSS、JS 文件
- 用户上传的头像、附件
- 接口文档引用的图片资源
- favicon 等站点图标
FastAPI 基于 Starlette,而 Starlette 提供了 StaticFiles 这个 ASGI 应用,可以直接挂载到 FastAPI 的路由体系中使用。
基础用法:挂载目录
StaticFiles 位于 starlette.staticfiles(也可从 fastapi.staticfiles 导入),使用 app.mount() 将其挂载到指定路径前缀下:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# 将本地的 static 目录挂载到 /static 路径
app.mount("/static", StaticFiles(directory="static"), name="static")
目录结构假设如下:
project/
├── main.py
└── static/
├── logo.png
├── style.css
└── images/
└── banner.jpg
挂载后访问规则:
| 请求路径 | 对应本地文件 |
|---|---|
/static/logo.png |
static/logo.png |
/static/style.css |
static/style.css |
/static/images/banner.jpg |
static/images/banner.jpg |
注意:app.mount() 挂在指定路径下的 子应用,该路径本身不会被 FastAPI 的路由处理 — 完全由 StaticFiles 接管。
关键参数详解
StaticFiles 构造函数的常用参数:
StaticFiles(
directory="static", # 必填:本地文件目录
packages=None, # 可选:Python 包列表(如 Jinja2 静态资源)
html=False, # 是否将 directory 下的 HTML 文件当作文本返回
check_dir=True, # 启动时检查目录是否存在
follow_symlinks=False, # 是否跟随符号链接
)
html 参数
当 html=True 时,访问 index.html 等文件会以 text/html Content-Type 返回,适合挂载前端 SPA 的打包产物:
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
packages 参数
用于引用已安装 Python 包内的静态资源:
app.mount("/static", StaticFiles(packages=[("my_package", "static")]), name="static")
这会查找 my_package 包内的 static 子目录。
挂载顺序与路由优先级
挂载路径优先于路由。Starlette 按注册顺序匹配,一旦某个 mount 路径前缀命中,后续路由就不会再检查。错误示例:
app = FastAPI()
# ❌ 错误:先挂载 /,会吞掉所有 API 路由
app.mount("/", StaticFiles(directory="dist", html=True), name="frontend")
@app.get("/api/hello") # 永远不会被命中!
async def hello():
return {"message": "Hello"}
正确做法是 API 路由注册在前,静态文件挂载在后(或者用不同路径前缀):
app = FastAPI()
# ✅ API 路由在前
@app.get("/api/hello")
async def hello():
return {"message": "Hello"}
# ✅ 静态文件挂载在后,且路径前缀不冲突
app.mount("/", StaticFiles(directory="dist", html=True), name="frontend")
或者更清晰的方式 — 使用不同前缀:
app = FastAPI()
@app.get("/api/hello")
async def hello():
return {"message": "Hello"}
app.mount("/app", StaticFiles(directory="dist", html=True), name="frontend")
运行时动态添加静态目录
如果需要根据运行时条件挂载不同目录(如按用户区分的上传目录),可以在路由层手动构造 StaticFiles 应用后挂载:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import os
app = FastAPI()
@app.get("/api/hello")
async def hello():
return {"message": "Hello"}
# 假如通过环境变量控制静态文件目录
static_dir = os.getenv("STATIC_DIR", "static")
app.mount("/static", StaticFiles(directory=static_dir), name="static")
如果需要更复杂的逻辑(如按用户 id 分流),则不适合用 mount,应改用 FileResponse 在路由中手动处理文件返回。
配合 FileResponse 精确控制
对于需要权限校验或动态路径的单个文件返回,使用 FileResponse 而非全局挂载:
from fastapi import FastAPI
from fastapi.responses import FileResponse
import os
app = FastAPI()
@app.get("/download/{filename}")
async def download_file(filename: str):
file_path = os.path.join("uploads", filename)
if not os.path.isfile(file_path):
return {"error": "文件不存在"}
return FileResponse(
path=file_path,
filename=filename,
media_type="application/octet-stream"
)
FileResponse 支持的关键参数:
| 参数 | 说明 |
|---|---|
path |
文件路径 |
filename |
下载时显示的文件名(触发 Content-Disposition) |
media_type |
显式指定 MIME 类型 |
headers |
自定义响应头字典 |
挂载多个静态目录
FastAPI 支持同时挂载多个静态目录到不同路径:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
@app.get("/api/health")
async def health():
return {"status": "ok"}
# 前端资源
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
# 用户上传文件
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
# 静态资源
app.mount("/static", StaticFiles(directory="static"), name="static")
各挂载点互不干扰,各自管理自己的目录。
生产环境注意事项
StaticFiles 使用 Python 的 aiofiles 做异步文件 I/O,在开发和小规模场景足够。但在高并发生产环境中,建议:
- 由 Nginx / Caddy 等反向代理直接处理静态文件请求
- FastAPI 只处理 API 逻辑,静态文件路径在反向代理层拦截
Nginx 配置示例片段:
server {
listen 80;
server_name example.com;
# 静态文件由 Nginx 直接返回
location /static/ {
alias /app/static/;
expires 30d;
}
# API 请求转发给 FastAPI
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}
}
总结
| 场景 | 推荐方案 |
|---|---|
| 全局静态资源目录 | app.mount("/static", StaticFiles(...)) |
| 前端 SPA 部署 | app.mount("/", StaticFiles(..., html=True)) |
| 需要权限控制的文件下载 | 路由中使用 FileResponse |
| 高并发生产环境 | Nginx 直接处理,FastAPI 只做 API |
掌握 StaticFiles 和 FileResponse 两种方式,就能灵活应对从开发到生产的不同静态文件需求。
- 点赞
- 收藏
- 关注作者
评论(0)