跳到内容

准确计算 multipart/form-data 的开销

分类:HTTP·通信
本文目前仅提供日文版本。我们正在进行翻译工作。

在文件上传大小限制测试中,您是否遇到过「文件只有 9MB 却出现 413 错误」的情况?其中一个原因是 <code>multipart/form-data</code> 的开销。通过 HTML 表单上传文件时,HTTP 请求体不仅包含文件本身,还包含额外的元数据。本文介绍了该开销的精确计算方法和测试时的注意事项。

multipart/form-data 的结构

RFC 2046 中定义的 <code>multipart/form-data</code> 具有每个部分由边界字符串分隔的结构。实际的 HTTP 请求正文如下所示。

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 10000xyz

------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="file"; filename="test.png"
Content-Type: image/png

[ファイルのバイナリデータ]
------WebKitFormBoundaryABC123--

开销明细

典型文件上传请求的开销由以下元素组成。

元素示例字节数(参考值)
起始边界------WebKitFormBoundaryABC123\r\n约 40–80 B
Content-Disposition 头Content-Disposition: form-data; name="file"; filename="test.png"\r\n约 60–120 B
Content-Type 头Content-Type: image/png\r\n约 25–50 B
空行(标头末尾)\r\n2 B
部分末尾的换行符\r\n2 B
结束 boundary------WebKitFormBoundaryABC123--\r\n约 42–82 B
<strong>总开销</strong><strong>约 200~350 B</strong>

对于仅包含一个文件的简单表单,开销约为 <strong>200–400 字节</strong>。如果存在额外的表单字段(如文本输入),开销会相应增加,但通常保持在几百字节到几千字节之间。

为什么发生413错误

Nginx 的 <code>client_max_body_size</code> 限制<strong>整个请求体</strong>的大小。也就是说,配置值必须包括开销。

# 10MiB のファイルをアップロードさせたい場合
# オーバーヘッド(約1KB)を考慮して少し大きめに設定
client_max_body_size 11m;  # MiB単位: 11 MiB = 11,534,336 バイト

对于PHP,需要配置两个设置:<code>upload_max_filesize</code>(单个文件)和<code>post_max_size</code>(整个POST体)。

; php.ini
upload_max_filesize = 10M   ; ファイル単体の上限: 10 MiB
post_max_size = 11M         ; POSTボディ全体の上限: 11 MiB(オーバーヘッド分を加算)

准确的开销测量方法

如果您想准确了解实际开销,可以使用 <code>curl</code> 发送请求并测量请求大小。

# ファイルのバイト数を確認
wc -c test-10mb.png
# → 10485760 test-10mb.png

# curl でアップロードしてリクエストサイズを確認
curl -X POST https://example.com/upload \
  -F "file=@test-10mb.png" \
  -w "リクエストボディサイズ: %{size_upload} バイト\n" \
  -o /dev/null -s
# → リクエストボディサイズ: 10486062 バイト(差: 302バイト)

与 Base64 编码的区别

使用 <code>multipart/form-data</code>,二进制文件数据按原样发送(不进行 Base64 编码)。Base64 编码仅在使用 <code>application/x-www-form-urlencoded</code> 发送二进制或在 JSON 主体中发送文件(如 <code>data:image/png;base64,...</code>)时才需要。

传输方式开销用途
multipart/form-data(HTML 表单)数百 B ~ 数 KB标准文件上传
JSON + Base64约增加 33%通过 API 传输文件
application/octet-stream(PUT)接近零S3 预签名 URL 等

测试要点

  • 了解服务器的大小限制检查是针对"单个文件"还是"整个请求体"
  • 由于Nginx在PHP前段限制请求,也要检查Nginx的<code>client_max_body_size</code>
  • 多个文件的同时上传会增加开销
  • 开销中也包括额外的表单字段(姓名、评论等)

使用 DevLab 的 <a href="/ja/files/threshold/">阈值测试文件</a>,您可以验证接近边界的文件大小的实际上传行为。请与 Nginx/Apache/PHP 配置一起验证。

总结

  • <code>multipart/form-data</code> 的开销约为 <strong>200–400 字节</strong> (单个文件的情况下)
  • 由于 Nginx 的 <code>client_max_body_size</code> 限制整个请求体,设置它应稍大于文件大小限制
  • PHP 需要设置两个指令:<code>upload_max_filesize</code>(单个文件)和 <code>post_max_size</code>(整个 POST 正文)。
  • 通过 JSON + Base64 发送时,文件大小增加约 33%

本文中可用的测试文件

  • → <a href="/ja/files/threshold/" class="text-primary-600 dark:text-primary-400 hover:underline">边界值测试文件列表(9.9MB / 10MB / 10.1MB)</a>
  • → <a href="/ja/files/images/png/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 图像测试文件列表</a>

相关文章

  • → <a href="/ja/blog/how-to-test-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">如何正确测试文件上传限制</a>
  • → <a href="/ja/blog/mb-vs-mib-file-size/" class="text-primary-600 dark:text-primary-400 hover:underline">MB 和 MiB 不一样!文件大小单位的陷阱</a>