准确计算 multipart/form-data 的开销
在文件上传大小限制测试中,您是否遇到过「文件只有 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\n | 2 B |
| 部分末尾的换行符 | \r\n | 2 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>