HTTP 请求体与 Content-Type
请求体(Request Body)是客户端发给服务器的"货物"——登录表单的账号密码、注册接口的用户信息、上传的图片,全靠它承载。而Content-Type 头就是这批货物的"标签",告诉服务器该怎么解析。
1. 哪些方法有 body?
- 有 body:
POST、PUT、PATCH。 - 通常无 body:
GET、DELETE、HEAD。协议不禁止,但实际中很少用,且很多代理/服务器会丢弃或拒绝。
2. Content-Type 决定 body 格式
同样的字符串 name=Bob,用不同 Content-Type 发,含义完全不同。服务器必须按头部声明的格式解析,否则报错。常见的 4 种:
3. application/json — 现代 API 首选
RESTful API、GraphQL、微服务通信90% 用 JSON。结构清晰、可读、语言无关。
# 1. application/json — 现代 API 最常用
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 36
{"name":"Bob","email":"b@x.com"}
# fetch:
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Bob', email: 'b@x.com' })
})
# curl:
curl -X POST https://api.example.com/api/users \
-H 'Content-Type: application/json' \
-d '{"name":"Bob","email":"b@x.com"}'注意字符集:JSON 标准要求UTF-8,所以一般不写 charset=utf-8,但写了也无害。
4. application/x-www-form-urlencoded — 表单默认
HTML <form> 不指定 enctype 时就是这种格式。它把数据编码成 key=value&key2=value2,特殊字符用 percent-encoding(%20 是空格、%40 是 @)。
# 2. application/x-www-form-urlencoded — HTML 表单默认
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
username=alice&password=1234
# 数据格式: key=value & key=value,特殊字符用 percent-encoding
# 比如 "a b@c" 会编码成 "a+b%40c" 或 "a%20b%40c"
# fetch 写法:
fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ username: 'alice', password: '1234' })
})
# curl 不加 -H 时,默认就是这种格式:
curl -X POST https://example.com/login \
-d 'username=alice&password=1234'这种格式不能直接传文件(二进制没法 URL 编码),所以传统表单上传文件必须切换成 multipart。
5. multipart/form-data — 上传文件必备
当表单要传文件,加 enctype="multipart/form-data"。body 被分隔符(boundary)切成多个 part,每个 part 可以是普通字段或文件,文件 part 还能带自己的 Content-Type。
# 3. multipart/form-data — 上传文件/混合字段
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 234
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="title"
我的头像
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="file"; filename="avatar.png"
Content-Type: image/png
(这里是文件的二进制字节)
------WebKitFormBoundaryABC123--
# fetch 用 FormData 自动处理 boundary:
const fd = new FormData();
fd.append('title', '我的头像');
fd.append('file', fileInput.files[0]);
fetch('/upload', { method: 'POST', body: fd });
# 注意: 用 FormData 时千万不要手动设 Content-Type,
# 浏览器会自动加上带正确 boundary 的头实战要点:
- boundary 是随机生成的字符串,不能出现在 body 内容里。
- 用
FormData时不要手动设 Content-Type——浏览器会自动加上带正确 boundary 的头,手动设反而会破坏它。 - multipart 比 JSON 体积稍大(每个 part 都有头开销),但能传二进制。
6. text/plain 与 application/octet-stream
# 4. text/plain — 纯文本 (用得少)
POST /log HTTP/1.1
Host: example.com
Content-Type: text/plain; charset=utf-8
Content-Length: 13
hello, server
# 5. application/octet-stream — 二进制流 (上传任意文件)
POST /files HTTP/1.1
Host: example.com
Content-Type: application/octet-stream
(原始字节)
# curl 上传二进制文件:
curl -X POST https://example.com/files \
--data-binary @/path/to/file.bintext/plain:纯文本,几乎不用(部分日志上报接口用)。application/octet-stream:任意二进制流。常用于"把整个文件作为一个流上传",不带额外元数据。
7. 上传文件实战
# 用 curl 上传文件 (multipart)
curl -X POST https://example.com/upload \
-F 'title=我的头像' \
-F 'file=@/path/to/avatar.png'
# -F 等价于 --form,会自动用 multipart/form-data
# @前缀表示 "这个值是文件路径"
# fetch 上传多个文件:
const fd = new FormData();
for (const f of fileInput.files) {
fd.append('files', f); // 同名追加多个
}
fetch('/upload', { method: 'POST', body: fd });8. 响应也有 Content-Type
响应同样用 Content-Type 描述 body 类型。浏览器据此决定怎么渲染:
text/html→ 当网页渲染application/json→ 当 JSON 解析(fetch 用res.json())image/png→ 当图片显示application/pdf→ 当 PDF 打开application/octet-stream→ 当下载文件
当响应头加上 Content-Disposition: attachment; filename="x.csv",浏览器会强制下载(而不是预览)。
9. body 大小限制
# 请求体大小限制
# 1. URL 长度 (~2K-8K): 影响 GET 查询串,不影响 body
# 2. 服务器有 body 限制:
# Nginx 默认 client_max_body_size 1m (返回 413)
# Express 默认 100kb (body-parser limit)
# Django 默认 DATA_UPLOAD_MAX_MEMORY_SIZE 2.5MB
# 3. 浏览器基本无限制 (除非内存不够)
#
# 大文件上传:
# - 改服务器配置
# - 分片上传 (slice + 多个 PATCH)
# - 直传对象存储 (S3 预签名 URL)遇到 413 Payload Too Large,基本都是服务器配置问题,不是协议限制。
10. Content-Length vs chunked
body 怎么结束?两种方式:
- Content-Length:声明 body 字节数。最常见。
- Transfer-Encoding: chunked:分块传输。常用于服务器流式响应(实时日志、AI 流式回复)。请求里也能用,但很少。
详见第 2 篇 HTTP 报文结构。
11. 常见错误
- 415 Unsupported Media Type:Content-Type 服务器不接受(如服务器要 JSON,你发了表单)。
- 400 Bad Request:Content-Type 声明 JSON,但 body 不是合法 JSON。
- 中文乱码:Content-Type 没声明 charset,服务器用了错误编码(GBK vs UTF-8)。
- fetch 上传 FormData 一直失败:99% 是你手动加了
Content-Type头,删掉就好。 - 413 Payload Too Large:服务器限制了 body 大小。
小结
选 Content-Type 的一句话:JSON 接口用 application/json,传统表单用 x-www-form-urlencoded,上传文件用 multipart/form-data,纯二进制流用 application/octet-stream。fetch 用 FormData 时千万别手动设 Content-Type。
← 上一篇 HTTP 头部
下一篇 Cookie 与 Session →