FastAPI 进阶之路:文件上传、Request、Response 与 Cookies
这篇博客还原了学习 FastAPI 时的问题。内容不仅涵盖了笔记中关于 文件上传(bytes 与 UploadFile 的生死选择)和 Request/Response 的基础架构,还特别针对开发实战补充了 Cookies 与 Session 的“会员制”管理逻辑 。我将用最生活化的学生口吻,帮你梳理如何优雅地处理多文件保存、如何利用 response_model 过滤敏感数据。
哈喽大家好!
上次我们聊了 FastAPI 里的 APIRouter 和 Pydantic,这次继续往更实用的部分走一走:文件上传(File Upload)、请求(Request)、响应(Response)以及经常让人一脸问号的 Cookies。
一开始看这些内容,感觉无非就是“收数据、回数据”。但真写起来才发现,事情远没那么简单:类型不对直接报错、少写一个 await 程序就开始摆烂、大文件一上传服务器内存直接顶不住……
所以这次就把这些内容简单梳理一遍,把常用的用法和基本思路理清楚,方便后面实际开发的时候不至于一脸懵。
文章目录
1、 文件上传:别只用 bytes
在 FastAPI 里做文件上传,主要有两种方式。bytes 和 UploadFile,我们刚开始会分不清啥时候该用哪个 。
1.1 bytes:轻量级但有风险
如果你只需要上传一个几 KB 的头像或者一段简单的文本配置,用 bytes 是最直观的。
# 文件上传
# bytes类型:表示二进制数据,通常用于小文件上传
@api_router.post('/upload')
async def upload_file(file: UploadFile = Form(...)):
"""
单文件上传
:param file:
:return:
"""
content = await file.read()
print(content)
return {
'file_size': len(content),
'message': '上传成功'
}
为什么说它有一定风险?
因为当参数类型是 bytes 时,FastAPI 会把整个文件一次性加载到内存中。一旦上传文件较大,就会快速占用大量内存资源,可能影响服务稳定性。
1.2 UploadFile:这才是真正的“生产级”工具
它的优势在哪?
内存友好:它使用了一个“分块”机制。小文件存内存,超过一定限制(默认 1MB 左右)就自动存到硬盘的临时文件里 。
元数据丰富:你可以直接拿到文件名(filename)、文件头(content_type) 。
类文件对象:它有 read()、write()、seek()、close() 这些方法,用起来跟 Python 原生的 open() 非常像 。
在实际开发中,上传多个文件是很常见的需求,比如头像批量上传、附件提交等。在 FastAPI 中,实现起来其实很直接:
@api_router.post('/upload_multiple')
async def upload_file(files: list[UploadFile] = Form(...)):
"""
多文件上传
:param files:
:return:
"""
if len( files ) > 2:
return {
'message': '最多只能上传两个文件'
}
sizes = []
for file in files:
content = await file.read()
sizes.append(len(content))
print(file.filename, len(sizes))
return {
'file_count': len(sizes),
'file_sizes': sizes,
'message': '上传成功'
}
核心点解释
- List[UploadFile]:表示可以接收多个文件
- UploadFile:相比 bytes 更适合处理文件(不会一次性全部读入内存)
- await file.read():异步读取文件内容
不过这里有个“隐藏问题”:
你只是把文件读进内存了,但并没有保存下来。
客户上传文件的流程图如下:
说白了就是:
你不保存,它就像你没保存的 Word 文档一样,关了就没了。
可以使用aiofiles:
import aiofiles
@user_router.post("/upload/save/")
async def upload_and_save(file: UploadFile = File(...)):
async with aiofiles.open(f"uploads/{file.filename}", "wb") as out_file:
content = await file.read()
await out_file.write(content)
return {"msg": "upload success"}
UploadFile:负责接收文件
read():只是把文件读进内存
真正的上传 = 读取 + 写入磁盘
2、 Request 对象:客户端到底给我传了啥?
Request 是客户端请求的封装。其实,你可以把它想象成快递盒。这个盒子上贴了面单(Headers),里面装了货物(Body),还有一些隐藏的配送信息(Client IP, Cookies 等) 。
2.1 为什么有时候必须显式使用 Request?
虽然 FastAPI 提倡“依赖注入”,但有些时候你必须拿到整个 Request 对象:获取客户端 IP 地址、读取原始 Headers 或者批量操作 Cookies 。
3、Cookies:Web 世界里的“会员小纸条”
很多人分不清 Cookie 和 Session,甚至觉得 Cookie 很麻烦,其实它就是服务器塞给浏览器的一行字符串。
3.1 核心原理:谁在记账?
笔记里提到:“一旦禁用 Cookie,功能直接失效” 。
生活化的理解:
你第一次去咖啡店(服务器),店员不认识你。
你点完单,店员给你一张会员卡(Set-Cookie),上面写着你的 ID:123。
下次你去的时候,你主动亮出这张卡(请求头里的 Cookie),店员一看:噢!是 123 号老客户,还是爱喝冰美式。
3.2 如何读取 Cookie?
在 FastAPI 里,读取 Cookie 极其简单,直接用 Cookie() 注入就行。
from fastapi import Cookie
@app.get("/items/")
async def read_items(ads_id: str | None = Cookie(default=None)):
# 这里的 ads_id 会自动寻找请求中名为 'ads_id' 的 cookie
return {"ads_id": ads_id}
3.3 如何写入(设置)Cookie?
写入 Cookie 不能像返回 JSON 那样随手一写,你必须操作 Response 对象 。
from fastapi import Response
@app.post("/login/")
async def login(response: Response):
# 模拟登录逻辑...
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return {"message": "登录成功,Cookie已发送"}
4、 Response 对象:给结果也要给得“优雅”
| request | response | |
|---|---|---|
| 谁发的 | 客户端 | 服务器 |
| 作用 | 提需求 | 给结果 |
| 内容 | headers + body | status + headers + body |
但作为开发者,我们不能只给个简单的 JSON。有时候我们要跳页面,有时候我们要用户下载图片。
4.1 常见的响应类型及其应用场景
(1) JSONResponse
FastAPI 默认返回的就是它。它会自动把 Python 的字典转成 JSON 字符串。
补充: 如果你想返回特定的状态码(比如 201 Created),就得显式用到它。
@Response_router.get('/json')
async def get_json_response():
# 返回json格式数据
return JSONResponse(
content={
'message': 'json response'},
status_code=200
)
(2) HTMLResponse(偶尔客串前端)
虽然 FastAPI 主要是写后端接口的,但有时候我们需要返回一个简单的“注册成功”页面,或者动态生成一个确认页。
@Response_router.get('/html')
async def get_html_response():
# 返回html格式数据
return HTMLResponse(
content="<h1>Hello</h1>"
)
(3) RedirectResponse(“请移步这里”)最重要!!!
-
场景一:用户没登录,直接重定向到 /login。
-
场景二:旧的 API 废弃了,重定向到新的接口。
@Response_router.post('/login')
async def skip_response(user_id:str=Form(...),password:str=Form(...)):
if user_id == 'admin' and password == '123456':
return RedirectResponse(
url = '/response/html',
status_code=302
)
else:
return PlainTextResponse(
content='登录失败',
status_code=401
)
(4) FileResponse(文件下载)
注意: 一定要设置 filename 参数,否则用户下载下来的文件可能叫 response_12345,没有后缀名。
@Response_router.get("/file")
def get_file():
# 文件下载:服务器 -> 客户端
# 指定文件路径(相对于项目根目录)
file_path = Path(__file__).parent.parent.parent / "test.txt"
return FileResponse(
path=str(file_path),
media_type='text/plain',
filename="download.txt"
)
5、深度补充:Response Model 的强大之处
response_model,是 FastAPI 的“灵魂”之一。
我们在写代码时,数据库里的用户数据可能包含密码(password)。我们绝对不能把密码也返回给前端。
通过 response_model,我们可以定义一个“过滤网”。
from pydantic import BaseModel
class UserOut(BaseModel):
id: int
username: str
# 这里不写 password,密码就不会被发出去!
@app.get("/user/{id}", response_model=UserOut)
async def get_user(id: int):
# 哪怕数据库查询结果里有 password
user_data = {"id": 1, "username": "admin", "password": "secret_password"}
return user_data # FastAPI 会根据 UserOut 自动帮我们过滤掉密码
6、 总结与心得
写到这里,提醒大家:路径后建议统一加上 “/”,这涉及到 Web 服务器的重定向规范 。
学习建议:
-
多看 Swagger(Yaak)界面:调试上传、Cookies 和 Session 非常直观 。
-
异步不能停:涉及 IO 操作,坚持用 async/await 。
-
校验第一位:配合 Pydantic,永远不要相信客户端传来的数据 !
参考资料:
-
FastAPI 官方文档
-
某些被我折磨过的 AI 同志
更多推荐
所有评论(0)