FastAPI 用 File() 的 max_size 参数可轻量拦截超限上传,单位字节,超限返回413;content_type校验比后缀更可靠;手动读取后需 seek(0);大文件应避免 await file.read() 防OOM。

用 File() 的 max_size 参数直接限制大小
FastAPI 内置支持通过 File() 的 max_size 参数做上传大小拦截,这是最轻量、最推荐的第一道防线。
它在 Starlette 的 form 解析层就生效,不等文件内容真正读入内存或临时文件,就能拒绝超限请求,避免资源浪费。
-
max_size单位是字节,比如限制 10MB 就写max_size=10*1024*1024 - 超出时自动返回
413 Payload Too Large,无需手动抛异常 - 注意:该参数只对
UploadFile类型字段有效,不能用于普通str或bytes - 如果同时用了
File(max_size=...)和自定义校验逻辑,max_size会先触发,后续逻辑根本不会执行
校验 content_type 比仅看后缀更可靠
用户可以随意改文件后缀,所以单靠 filename.rsplit('.', 1)[1] 判断类型容易绕过。应优先检查 UploadFile.content_type 字段,它是 HTTP 请求中真实的 MIME 类型(由浏览器或客户端生成)。
例如图片上传,允许 "image/jpeg"、"image/png",但拒绝 "text/plain" 即使后缀是 .jpg。
- 白名单建议用集合:
ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"} - 不要用
if file.content_type.startswith("image/")这类宽松匹配,可能放行image/svg+xml等非预期类型 - 如果业务强依赖后缀(如某些老系统),可双校验:先验
content_type,再辅以后缀检查,但后者必须和前者一致(比如content_type=="image/png"时,后缀必须是.png)
手动读取前必须 seek(0),否则后续读不到内容
很多开发者在 validate_file() 里调用 file.file.read() 做大小或内容校验后,忘了重置文件指针,导致后续保存时 shutil.copyfileobj(file.file, buffer) 读到空数据。
这是因为 UploadFile.file 是一个类似 SpooledTemporaryFile 的对象,读一次指针就跑到末尾了。
- 每次手动
read()后,务必加file.file.seek(0) - 更安全的做法是:校验用
await file.read()(异步方式),它会自动管理缓冲;之后再用await file.seek(0)重置(UploadFile支持该方法) - 如果用同步
shutil.copyfileobj()保存,确保前面没混用异步read(),否则可能引发 RuntimeError:「Event loop is closed」
大文件上传慎用 await file.read()
当 max_size 设为 500MB,而用户真传了个 499MB 文件时,await file.read() 会把整个文件加载进内存——不是流式处理,而是全量载入。
这极易触发 OOM(内存溢出),尤其在容器或低配服务器上。
- 对明确的大文件场景(如视频、数据库备份),应跳过
File(max_size=...),改用原始 request body 流式读取 - 即不用
UploadFile,而用Request接收,配合iter_chunks()分块校验 size 和 type - 此时 MIME 类型需从
request.headers.get("content-type")解析 multipart boundary,再逐块提取 header 判断,复杂度显著上升
max_size 和 content_type 校验能覆盖 95% 的常规需求;但只要接口暴露给不可信用户,就必须意识到:所有前端限制都可被绕过,服务端校验不能少,而内存安全边界往往比格式检查更容易被忽略。


















