UploadFile处理超大文件不可靠,因其依赖form解析会缓存全量请求体至内存或磁盘;应改用Request.stream()流式读取原始body,配合分块上传与校验实现GB级健壮传输。

直接用 UploadFile 接收超大文件(比如 >500MB)是危险的——它默认走 form 解析,会把整个请求体缓存到内存或临时磁盘,极易触发 RequestEntityTooLarge 或耗尽服务器资源。真正在生产中扛住 GB 级上传,必须绕过 form 解析,自己流式读取原始 body。
为什么 UploadFile 在超大文件场景下不可靠
UploadFile 本质是 Starlette 的封装,底层依赖 python-multipart 解析 multipart boundary。这个过程:
- 必须完整接收并解析整个 HTTP body 才能提取文件字段,无法“边收边处理”
- 小文件走内存缓冲(
SpooledTemporaryFile),但一旦超过max_memory(默认 10MB),就写入磁盘临时文件——而这个临时路径、清理逻辑、磁盘空间监控全由 Starlette 控制,你无法干预 - 如果客户端上传中断,Starlette 不会自动清理已写入的临时碎片,可能堆积大量孤儿文件
正确做法:用 Request.stream() 手动流式读取
去掉所有 UploadFile 参数,改用 Request 对象直接消费原始字节流:
关键点:
- 用
request.stream()(Starlette 原生方法,不是 FastAPI 封装的)获取异步迭代器,每次 yield 一个 chunk - 绝对不要用
await request.body(),它会强制加载全部内容到内存 - 写入目标文件时,用
asyncio.to_thread(open(...).write)避免阻塞事件循环 - 自己控制分块大小(如 8MB)、校验(如每块算 CRC)、超时和中断恢复逻辑
示例片段:
from fastapi import Request, Response
import asyncio
<p>@app.post("/upload/raw")
async def raw_upload(request: Request):
file_id = request.headers.get("X-File-ID")
if not file_id:
return Response("Missing X-File-ID", status_code=400)</p><pre class="brush:php;toolbar:false;">chunk_size = 8 * 1024 * 1024
target_path = f"/data/uploads/{file_id}.part"
async def stream_to_file():
async for chunk in request.stream():
# 每次 chunk 是 bytes,大小不固定(取决于网络包)
# 这里可加校验、限速、日志等
await asyncio.to_thread(write_chunk, target_path, chunk)
await stream_to_file()
return {"status": "received"}
部署层必须同步调大限制,否则请求根本到不了 FastAPI
很多人在 FastAPI() 初始化里找上传大小配置,找不到是对的——FastAPI 本身不设这个。真正生效的限制在部署组件:
-
Uvicorn:启动时加--limit-concurrency 100 --limit-max-requests 0不够,要配--http-timeout 3600和--backlog 2048;更关键的是--limit-request-line 8190 --limit-request-fields 100(影响 header 解析) -
Nginx:必须设client_max_body_size 4G;和client_body_timeout 3600s;,否则 413 错误在 Nginx 就拦截了 -
Cloudflare:免费版默认限制 100MB,需升级或绕过(如用 Workers 中转)
分块上传不是“可选优化”,而是超大文件的唯一可行路径
即使你用 Request.stream() 流式读取,单次 HTTP 请求仍面临现实约束:TCP 超时、代理重置、客户端断连。真正的健壮方案必须分块:
- 前端按固定大小(如 5MB)切片,每块带
X-File-ID、X-Chunk-Index、X-Total-Chunks头 - 后端每个块单独接收、落盘、记录状态(推荐用 Redis 存
{file_id}:status和{file_id}:chunksSet) - 合并操作必须原子化:先写临时合并文件,再
os.replace()替换最终文件,避免中间态被读取 - 断点续传靠查询 Redis 中已上传的 chunk index 列表,前端跳过即可
最易被忽略的一点:分块上传的“完成”判定不能只看 chunk 数量是否达标——要逐个验证每个块的 SHA256,否则网络损坏的数据会被静默拼接进去。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











