Multipart Form Data:全方位指南 (2026)
关于 HTTP 请求中 multipart/form-data 你需要了解的一切
开始使用 Hypereal 构建
通过单个 API 访问 Kling、Flux、Sora、Veo 等。免费积分开始,扩展到数百万。
无需信用卡 • 10万+ 开发者 • 企业级服务
Multipart Form Data:完整指南 (2026)
多部分表单数据 (multipart/form-data) 是在 HTTP 请求中发送文件和混合数据类型的标准方式。无论你是向 API 上传图片、提交带有文件附件的表单,还是在文本字段旁发送二进制数据,multipart/form-data 都是实现这一功能的机制。
本指南涵盖了开发者需要了解的所有内容:其底层逻辑、如何在各种主流语言和工具中发送它、常见陷阱以及最佳实践。
什么是 Multipart Form Data?
multipart/form-data 是一种 MIME 内容类型,用于对包含文件、二进制数据或文本与二进制混合内容的表单数据进行编码。它最初是为 HTML 表单提交设计的,但现在广泛应用于 REST API 中。
何时使用 Multipart Form Data
| 使用场景 | 内容类型 (Content Type) | 何时使用 |
|---|---|---|
| 上传文件 | multipart/form-data |
任何类型的文件上传 |
| 包含文件 + 文本的表单 | multipart/form-data |
混合文件和文本字段 |
| 仅发送 JSON | application/json |
不含文件的 API 请求 |
| 仅发送纯文本 | application/x-www-form-urlencoded |
简单的键值对表单数据 |
| 仅发送二进制数据 | application/octet-stream |
单个二进制文件,无元数据 |
经验法则: 只要请求中包含至少一个文件,就使用 multipart/form-data。对于纯数据请求,请使用 application/json。
Multipart Form Data 的工作原理
多部分请求将请求体拆分为多个“部分” (parts),由唯一的边界 (boundary) 字符串分隔。每个部分都有自己的请求头和内容。
原始 HTTP 请求结构
POST /api/upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"
My Photo
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
A sunset photo taken in Hawaii
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sunset.jpg"
Content-Type: image/jpeg
<binary file data>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
关键组件
| 组件 | 用途 | 示例 |
|---|---|---|
| Boundary | 分隔每个部分 | ----WebKitFormBoundary7MA4YWxkTrZu0gW |
| Content-Disposition | 命名字段和文件名 | form-data; name="file"; filename="photo.jpg" |
| Content-Type (分部分) | 该部分的 MIME 类型 | image/jpeg, application/pdf |
| Part body | 实际数据 | 文本字符串或二进制内容 |
边界是一个随机字符串,绝不能出现在任何部分的请求体中。代码库会自动生成此字符串——你几乎永远不需要手动设置它。
发送 Multipart Form Data:代码示例
cURL
# 上传带有元数据的文件
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@/path/to/photo.jpg" \
-F "title=My Photo" \
-F "description=A sunset photo"
# 上传多个文件
curl -X POST https://api.example.com/upload \
-F "files=@photo1.jpg" \
-F "files=@photo2.jpg" \
-F "album=Vacation"
# 明确指定 MIME 类型
curl -X POST https://api.example.com/upload \
-F "file=@document.pdf;type=application/pdf" \
-F "title=My Document"
关键点:
-F标志告诉 cURL 使用multipart/form-data@前缀表示从文件中读取- 如果不带
@,该值将作为文本字段发送
Python (requests)
import requests
# 上传单个文件
url = "https://api.example.com/upload"
headers = {"Authorization": "Bearer YOUR_TOKEN"}
with open("photo.jpg", "rb") as f:
files = {"file": ("photo.jpg", f, "image/jpeg")}
data = {"title": "My Photo", "description": "A sunset photo"}
response = requests.post(url, headers=headers, files=files, data=data)
print(response.json())
# 上传多个文件
files = [
("files", ("photo1.jpg", open("photo1.jpg", "rb"), "image/jpeg")),
("files", ("photo2.jpg", open("photo2.jpg", "rb"), "image/jpeg")),
]
data = {"album": "Vacation"}
response = requests.post(url, headers=headers, files=files, data=data)
# 在文件旁发送 JSON(常见的 API 模式)
import json
files = {"file": ("data.json", json.dumps({"key": "value"}), "application/json")}
data = {"metadata": "some info"}
response = requests.post(url, files=files, data=data)
重要提示: 使用 files 参数时,请勿手动设置 Content-Type。requests 库会自动设置包含正确边界字符串的请求头。
JavaScript (Fetch API)
// 浏览器:从输入元素上传文件
const form = document.querySelector("#uploadForm");
const fileInput = document.querySelector("#fileInput");
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("title", "My Photo");
formData.append("description", "A sunset photo");
const response = await fetch("https://api.example.com/upload", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_TOKEN",
// 不要设置 Content-Type -- 浏览器会自动设置带 boundary 的请求头
},
body: formData,
});
const result = await response.json();
console.log(result);
// 上传多个文件
const formData = new FormData();
const files = fileInput.files; // 来自 input[multiple] 的 FileList
for (const file of files) {
formData.append("files", file);
}
formData.append("album", "Vacation");
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
Node.js (原生 fetch, Node 18+)
import { readFile } from "fs/promises";
import path from "path";
const fileBuffer = await readFile("photo.jpg");
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
const formData = new FormData();
formData.append("file", blob, "photo.jpg");
formData.append("title", "My Photo");
const response = await fetch("https://api.example.com/upload", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_TOKEN",
},
body: formData,
});
const result = await response.json();
Go
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
// 创建缓冲区和 multipart writer
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// 添加文件字段
file, _ := os.Open("photo.jpg")
defer file.Close()
part, _ := writer.CreateFormFile("file", "photo.jpg")
io.Copy(part, file)
// 添加文本字段
writer.WriteField("title", "My Photo")
writer.WriteField("description", "A sunset photo")
writer.Close()
// 发送请求
req, _ := http.NewRequest("POST", "https://api.example.com/upload", &buf)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Rust (reqwest)
use reqwest::multipart;
use tokio::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file_bytes = fs::read("photo.jpg").await?;
let file_part = multipart::Part::bytes(file_bytes)
.file_name("photo.jpg")
.mime_str("image/jpeg")?;
let form = multipart::Form::new()
.part("file", file_part)
.text("title", "My Photo")
.text("description", "A sunset photo");
let client = reqwest::Client::new();
let response = client
.post("https://api.example.com/upload")
.header("Authorization", "Bearer YOUR_TOKEN")
.multipart(form)
.send()
.await?;
println!("{}", response.text().await?);
Ok(())
}
接收 Multipart Form Data (服务端)
Express.js (使用 Multer)
import express from "express";
import multer from "multer";
const app = express();
const upload = multer({ dest: "uploads/" });
// 单文件上传
app.post("/upload", upload.single("file"), (req, res) => {
console.log(req.file); // 文件信息
console.log(req.body.title); // 文本字段
res.json({ success: true, filename: req.file.filename });
});
// 多文件上传
app.post("/upload-many", upload.array("files", 10), (req, res) => {
console.log(req.files); // 文件对象数组
res.json({ success: true, count: req.files.length });
});
app.listen(3000);
Python (FastAPI)
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
title: str = Form(...),
description: str = Form(default="")
):
contents = await file.read()
return {
"filename": file.filename,
"size": len(contents),
"title": title,
"content_type": file.content_type
}
@app.post("/upload-many")
async def upload_files(
files: list[UploadFile] = File(...),
album: str = Form(...)
):
return {
"album": album,
"files": [f.filename for f in files],
"count": len(files)
}
Go (标准库)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 解析 multipart form,最大内存 32MB
r.ParseMultipartForm(32 << 20)
// 获取文件
file, header, _ := r.FormFile("file")
defer file.Close()
// 获取文本字段
title := r.FormValue("title")
fmt.Fprintf(w, "上传成功: %s (%d 字节), 标题: %s",
header.Filename, header.Size, title)
}
常见错误及如何避免
1. 手动设置 Content-Type
最常见的错误是手动设置 Content-Type 请求头。这会导致请求失败,因为手动设置的头部缺少或包含错误的边界字符串。
# 错误 - 不要手动设置 Content-Type
requests.post(url, headers={"Content-Type": "multipart/form-data"}, files=files)
# 正确 - 让库自动设置
requests.post(url, files=files)
// 错误 - 不要手动设置 Content-Type
fetch(url, {
headers: { "Content-Type": "multipart/form-data" },
body: formData,
});
// 正确 - 省略 Content-Type,浏览器会带上 boundary 自动设置
fetch(url, { body: formData });
2. 忘记以二进制模式打开文件
# 错误 - 文本模式会损坏二进制文件
files = {"file": open("photo.jpg", "r")}
# 正确 - 始终使用二进制模式
files = {"file": open("photo.jpg", "rb")}
3. 未关闭文件句柄
# 错误 - 文件句柄泄露
files = {"file": open("photo.jpg", "rb")}
requests.post(url, files=files)
# 文件句柄从未关闭
# 正确 - 使用上下文管理器
with open("photo.jpg", "rb") as f:
files = {"file": f}
requests.post(url, files=files)
4. 混合使用 data 和 json 参数 (Python)
# 错误 - json 参数与 files 冲突
requests.post(url, files=files, json={"title": "Photo"})
# 正确 - 在包含文件时,使用 data 参数处理文本字段
requests.post(url, files=files, data={"title": "Photo"})
Multipart vs. Base64 编码
某些 API 接受 JSON 中的 Base64 编码字符串作为文件,而不是 multipart 表单数据。以下是它们的对比:
| 维度 | Multipart Form Data | JSON 中的 Base64 |
|---|---|---|
| 尺寸开销 | ~0% (原始二进制) | ~33% (Base64 编码) |
| 流式支持 | 支持 | 不支持 (必须编码整个文件) |
| 标准性 | HTTP 原生 | 每个 API 自定义 |
| 浏览器支持 | 原生 (FormData) | 需要手动编码 |
| 服务端解析 | 内置中间件 | 自定义解析 |
| 文件大小限制 | 支持大文件 | 大文件时不切实际 |
何时使用 multipart: 文件上传、大文件、多文件、标准 REST API。
何时使用 base64: 小文件 (<1 MB)、要求纯 JSON 负载的 API、内联图像数据。
文件大小限制
大多数服务器和平台都会对多部分上传大小施加限制:
| 平台/服务器 | 默认限制 | 是否可配置 |
|---|---|---|
| Express.js (Multer) | 1 MB | 是 (limits: { fileSize: X }) |
| Nginx | 1 MB | 是 (client_max_body_size) |
| Apache | 2 MB | 是 (LimitRequestBody) |
| FastAPI/Uvicorn | 无硬性限制 | 是 (中间件) |
| AWS API Gateway | 10 MB | 固定 |
| Cloudflare | 100 MB (Pro 版) | 取决于方案 |
| Vercel | 4.5 MB (无服务器函数) | 固定 |
在构建文件上传功能时,请始终检查你的服务器和底层架构限制。
常见问题解答
multipart/form-data 和 application/x-www-form-urlencoded 有什么区别?
application/x-www-form-urlencoded 将所有数据编码为 URL 编码的键值对。它无法高效处理文件或二进制数据。multipart/form-data 将每个字段作为独立部分发送,原生支持文件和二进制数据。
我可以在多部分请求中发送 JSON 吗?
可以。你可以在文件部分旁包含一个 Content-Type: application/json 的部分。某些 API 使用此模式发送带有文件上传的结构化元数据。
为什么我的文件上传返回 413 (Payload Too Large)?
你的服务器或反向代理的文件大小限制低于你上传的文件。请检查 Nginx 的 client_max_body_size、Express 的 Multer 限制或你的托管平台限制。
在 API 中应该使用 multipart/form-data 还是 base64? 对于超过 1 MB 的文件上传或需要流式传输时,请使用 multipart。对于小型内联图像或当你的 API 架构要求纯 JSON 负载时,请使用 base64。
我可以通过 GET 请求上传文件吗? 不可以。在标准 HTTP 中,GET 请求没有请求体。文件上传必须使用 POST、PUT 或 PATCH 方法。
总结
Multipart form data 是开发者在构建文件上传功能或处理媒体 API 时必不可少的 HTTP 基础机制。核心规则非常简单:只要涉及文件,就使用它;让 HTTP 库设置 Content-Type 头部;以二进制模式打开文件;并始终关闭文件句柄。
如果你正在构建使用 AI 处理上传媒体的应用程序(如图像生成、视频创作或语音合成),欢迎免费试用 Hypereal AI —— 包含 35 个额度,无需信用卡。Hypereal 的 API 同时支持多部分表单数据和 JSON 负载,可轻松将 AI 媒体生成集成到任何上传工作流中。
