Content-Type 和 Accept 有什么区别?HTTP 内容协商是怎么做的?
简化版
Content-Type 表示当前请求体或响应体实际是什么格式,Accept 表示客户端希望接收什么格式。内容协商就是客户端用 Accept、Accept-Language、Accept-Encoding 等头表达偏好,服务端选择合适表示返回。
详细版
两者最容易混:
Content-Type:描述“我正在发送的 body 是什么”,例如application/json、application/x-www-form-urlencoded、multipart/form-data。Accept:描述“我希望你返回什么”,例如application/json、text/html。- 请求里可以同时有两者:POST JSON 请求希望返回 JSON,就会有
Content-Type: application/json和Accept: application/json。 Accept-Encoding用于压缩协商,如 gzip、br。Accept-Language用于语言偏好,如zh-CN、en-US。
如果请求体格式和 Content-Type 不匹配,服务端可能解析失败;如果服务端无法满足 Accept,可以返回 406。
完整版教学
一、先分清“发什么”和“想要什么”
Content-Type 和 Accept 都是 HTTP 头,但方向完全不同。Content-Type 描述当前消息体实际格式,Accept 描述客户端可接受的响应格式。
POST /api/users
Content-Type: application/json
Accept: application/json
{"name":"Tom"}
这里 Content-Type 告诉服务端按 JSON 解析请求体;Accept 告诉服务端最好也返回 JSON。二者不是互斥关系,而是经常一起出现。
记忆钩子:Content-Type 是“我给你的是什么”,Accept 是“我想从你那拿什么”。
二、Content-Type 错了会怎样
服务端通常根据 Content-Type 选择解析器。你明明发 JSON,却写成表单格式,后端就可能从表单解析器里拿不到字段。
| 请求体 | 正确 Content-Type | 常见用途 |
|---|---|---|
{"a":1} | application/json | API JSON |
a=1&b=2 | application/x-www-form-urlencoded | 普通表单 |
| 文件 + 字段 | multipart/form-data; boundary=... | 文件上传 |
| XML | application/xml | 老系统集成 |
举例:后端只接收 application/json,客户端发了 JSON 字符串但头是 text/plain,框架可能不会自动反序列化,最终报 415 或参数为空。
三、Accept 如何表达优先级
Accept 可以带 q 值表示偏好。q 值范围通常是 0~1,越大优先级越高。如果不写,默认权重可理解为 1。
Accept: application/json;q=1.0, text/html;q=0.8, */*;q=0.1
这表示客户端最想要 JSON,也能接受 HTML,实在不行接受任意格式。服务端会结合自己能生成的类型选择一个返回,并在响应里写 Content-Type。
四、压缩和语言也是内容协商
内容协商不只 MIME 类型。压缩、语言、字符集也可以协商。现代 Web 常见的是压缩和语言。
Accept-Encoding: br, gzip
Accept-Language: zh-CN, zh;q=0.9, en;q=0.5
Response:
Content-Encoding: br
Content-Language: zh-CN
压缩协商能显著减少传输体积。比如 100KB 的 JSON 经 gzip 可能变成 15KB~30KB,但服务端要付出 CPU 压缩成本,CDN 通常会缓存压缩后的版本。
五、Vary 头为什么重要
如果同一个 URL 会因为 Accept-Encoding 或 Accept-Language 返回不同内容,缓存系统必须知道“这个响应和哪些请求头有关”。Vary 就是告诉缓存 key 要纳入哪些头。
Vary: Accept-Encoding, Accept-Language
没有正确的 Vary,缓存可能把英文页面返回给中文用户,或把 gzip 内容返回给不支持 gzip 的客户端。内容协商和缓存是强相关的。
六、常见错误码怎么理解
内容协商失败时,常见两个状态码:415 Unsupported Media Type 和 406 Not Acceptable。前者偏请求体格式问题,后者偏响应格式无法满足。
| 状态码 | 谁的问题 | 例子 |
|---|---|---|
| 415 | 客户端发来的 body 格式不支持 | 服务端只收 JSON,客户端发 XML |
| 406 | 服务端无法返回客户端可接受格式 | 客户端只接受 XML,服务端只能 JSON |
| 400 | body 格式声明对,但内容语法错 | JSON 少了右括号 |
面试中把 415 和 406 区分开,是很加分的 HTTP 细节。
七、常见误区与追问
- 误区:Content-Type 和 Accept 都表示返回格式。 Content-Type 描述当前消息体,Accept 描述客户端期望的响应类型。
- 误区:GET 请求永远不需要 Content-Type。 GET 通常没有 body,所以少见;但响应仍然必须有 Content-Type。
- 误区:Accept-Encoding 和 Content-Encoding 是一回事。 前者是客户端可接受的压缩算法,后者是响应实际使用的编码。
- 追问:为什么上传文件要 multipart/form-data? 它能用 boundary 分隔多个字段和文件内容,适合混合表单。
- 追问:Vary 有什么用? 告诉缓存系统响应会随哪些请求头变化,避免缓存串内容。
- 追问:415 和 406 怎么区分? 415 是请求体媒体类型不支持,406 是响应无法满足 Accept。
八、加强记忆
HTTP 内容协商抓住三组对应关系:Content-Type 对“实际 body 类型”,Accept 对“期望响应类型”,Content-Encoding 对“实际压缩方式”。再补上 Vary 影响缓存,就能把 API 调试、文件上传、国际化和压缩都串起来。