multipart/form-data 是怎么上传文件的?和 application/json、表单编码有什么区别?
简化版
multipart/form-data 是浏览器表单上传文件时常用的请求体格式。
它会用一个 boundary 把请求体拆成多个 part,每个 part 都可以有自己的头信息,例如字段名、文件名、内容类型。普通文本字段和文件字段可以放在同一次请求里。
它适合上传二进制文件;application/x-www-form-urlencoded 更适合简单表单;application/json 更适合结构化 API 数据,但不能天然表达文件流。
详细版
典型请求头:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryabc
请求体中每个 part 由 boundary 分隔:
------WebKitFormBoundaryabc
Content-Disposition: form-data; name="name"
resume
------WebKitFormBoundaryabc
Content-Disposition: form-data; name="file"; filename="a.pdf"
Content-Type: application/pdf
...binary...
------WebKitFormBoundaryabc--
boundary 不能和正文内容冲突,最后一个 boundary 后面有 -- 表示结束。
文件上传接口常见问题包括大小限制、文件类型校验、临时文件存储、流式处理、安全扫描和超时控制。
完整版教学
1. 先说明 multipart 的本质
multipart/form-data 的核心是“一个 HTTP 请求体里包含多个独立片段”。
每个片段叫 part。
每个 part 可以表示一个普通字段,也可以表示一个文件。
浏览器提交带文件的表单时,一般会选择这种格式。
看到 multipart,就要想到 boundary 分隔、多 part、每个 part 有自己的头。
2. boundary 是怎么工作的
请求头会指定边界字符串:
Content-Type: multipart/form-data; boundary=----abc123
请求体里用这个边界切分内容:
------abc123
Content-Disposition: form-data; name="username"
tom
------abc123
Content-Disposition: form-data; name="avatar"; filename="me.png"
Content-Type: image/png
...binary bytes...
------abc123--
最后一段边界后面多出的 -- 表示整个 multipart body 结束。
服务端解析时会按照 boundary 拆分,然后分别读取每个 part 的元信息和内容。
3. Content-Disposition 很关键
每个 part 通常会带:
Content-Disposition: form-data; name="file"; filename="a.txt"
其中 name 是表单字段名,filename 是客户端上传时提供的文件名。
文件 part 还可能带:
Content-Type: text/plain
服务端不能盲信这些字段。
文件名可能包含特殊字符,Content-Type 也可能被伪造。
4. 和其他请求体格式对比
| 格式 | Content-Type | 适合内容 | 特点 |
|---|---|---|---|
| 表单编码 | application/x-www-form-urlencoded | 简单键值对 | 字段会 URL 编码 |
| JSON | application/json | 结构化数据 | 易读,适合 API |
| multipart | multipart/form-data | 文件 + 表单字段 | 支持二进制和多 part |
| 原始流 | application/octet-stream | 单个二进制流 | 元信息要另行传递 |
如果只是提交 {name, age},JSON 或表单编码都可以。
如果要同时传 userId 和文件,multipart 更自然。
5. 服务端处理文件上传的工程点
文件上传不是只解析 body。
服务端还要考虑:
- 最大请求体大小,例如限制 20MB。
- 单文件大小和文件数量限制。
- 文件类型白名单。
- 文件名规范化,避免路径穿越。
- 临时目录清理。
- 大文件流式写入,避免一次性读入内存。
- 上传后异步扫描或转码。
这些点经常是面试追问,因为它们连接协议和工程安全。
6. 安全风险不能忽略
上传文件最危险的点是“把用户提供的字节当可信内容”。
常见风险包括:
- 上传脚本文件后被服务器执行。
- 文件名包含
../造成路径穿越。 - 伪造 Content-Type 绕过校验。
- 超大文件压垮磁盘。
- 图片木马或恶意压缩包。
服务端应该使用随机文件名、独立对象存储、白名单校验、大小限制、权限隔离。
7. 常见误区与追问
- 误区:multipart 只能上传一个文件。 它可以包含多个文件 part,也可以混合普通字段。
- 误区:filename 就是安全文件名。 客户端传来的文件名不可信,不能直接拼路径。
- 误区:Content-Type 能证明文件真实类型。 它可以被伪造,必要时要检查魔数或做安全扫描。
- 追问:boundary 为什么必须出现在请求头里? 服务端需要它来准确切分请求体中的多个 part。
- 追问:JSON 能不能上传文件? 可以用 Base64 等方式绕,但体积变大且不适合大文件。
- 追问:大文件为什么要流式处理? 一次性读入内存会造成内存峰值和稳定性问题。
8. 加强记忆
multipart 的记忆模型是:一个大包裹,boundary 是隔板,每个 part 是一个小包。
答题时按 boundary、part 头、格式对比、服务端安全 4 层展开即可。