bigywater.tech
Flask · 35

文件上传与 FileStorage

一次文件上传到了 Flask 里是什么对象?有哪些属性?大文件放在哪?没选文件会怎样?save 有什么坑?这篇从 FileStorage 开始逐项打印,并用 curl 真实上传一次。

512000
SpooledTemporaryFile 的内存阈值(字节)
0
FileStorage.content_length 在 multipart 上传里的取值
空文件
save 第二次得到的内容(流已读到末尾)

FileStorage 的属性

实测 · 本机执行输出
  filename         = 'report 2024.pdf'
  name             = 'file'
  content_type     = 'application/pdf'
  mimetype         = 'application/pdf'
  content_length   = 0
  headers          = {'Content-Disposition': 'form-data; name="file"; filename="report 2024.pdf"', 'Content-Type': 'application/pdf'}
  stream_type      = 'SpooledTemporaryFile'
  bool             = True
  head             = 'HELLO'
  pos_before       = 0
  pos_after_read5  = 5
  size             = 2005
  saved_size       = 2005
  与原始数据的 sha256 前 12 位相同: True

上一篇:request.args、request.form、request.json;下一篇:多文件上传;文件名清洗见secure_filename() 的安全处理。

没选文件

实测 · 本机执行输出
  空 filename: {'bool': False, 'filename': '', 'len': None, 'present': True}
  根本没有 file 字段: {'bool': False, 'filename': None, 'len': None, 'present': False}
  视图里 request.files["file"] 缺失 → 400

内存还是磁盘

实测 · 本机执行输出
       1000 字节 → {'max_size': 512000, 'rolled': False, 'type': 'SpooledTemporaryFile'}
     400000 字节 → {'max_size': 512000, 'rolled': False, 'type': 'SpooledTemporaryFile'}
     600000 字节 → {'max_size': 512000, 'rolled': True, 'type': 'SpooledTemporaryFile'}
    5000000 字节 → {'max_size': 512000, 'rolled': True, 'type': 'SpooledTemporaryFile'}

save()

实测 · 本机执行输出
  save(路径) → b'content'
  save(文件对象) → b'content2'
  目标是已存在的目录: IsADirectoryError: [Errno 21] Is a directory: '<实验目录>/fltmp/f35_uploads'
  父目录不存在: FileNotFoundError: [Errno 2] No such file or directory: '<实验目录>/fltmp/f35_uploads/nodir/a.txt'
  同一个 FileStorage 连续 save 两次:第二次文件大小 = 0 (流读到末尾后没有复位,第二次是空文件)

真实 HTTP 上传

实测 · 本机执行输出
  curl -F doc=@hello.txt -F title=我的文档 → hello.txt|text/plain|13|我的文档
  curl -F "doc=@hello.txt;type=text/markdown;filename=改名.md" → 改名.md|text/markdown|13|None

分步演示

文件上传
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动
    • 没有验证:临时文件在请求结束时的清理时机与磁盘位置、浏览器真实发送的 multipart 边界与编码细节、非 ASCII 文件名在不同浏览器下的 Content-Disposition 编码(filename*=)、直接用 f.stream 与 shutil.copyfileobj 流式保存的耗时
    京ICP备2026067502号-1京公网安备11010502063487号