HTTP 请求体与 Content-Type

请求体(Request Body)是客户端发给服务器的"货物"——登录表单的账号密码、注册接口的用户信息、上传的图片,全靠它承载。而Content-Type 头就是这批货物的"标签",告诉服务器该怎么解析。

1. 哪些方法有 body?

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 的头

实战要点

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.bin

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 类型。浏览器据此决定怎么渲染:

当响应头加上 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 怎么结束?两种方式:

详见第 2 篇 HTTP 报文结构

11. 常见错误

小结

选 Content-Type 的一句话:JSON 接口用 application/json,传统表单用 x-www-form-urlencoded,上传文件用 multipart/form-data,纯二进制流用 application/octet-stream。fetch 用 FormData 时千万别手动设 Content-Type。

← 上一篇 HTTP 头部

下一篇 Cookie 与 Session

✈️💬