哈喽大家好!

上次我们聊了 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 同志

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐