常见请求 Content-Type 有哪些?JSON、FormData 和表单提交怎么选择?
简化版
常见请求体类型包括 application/json、application/x-www-form-urlencoded、multipart/form-data 和文本/二进制流。普通接口常用 JSON,传统表单可用 urlencoded,文件上传用 FormData 对应 multipart;使用 FormData 时不要手动写 Content-Type,浏览器会自动带上 boundary。
详细版
JSON 请求:
fetch('/api/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Tom' }),
})
文件上传:
const form = new FormData()
form.append('avatar', file)
form.append('name', 'Tom')
fetch('/api/upload', { method: 'POST', body: form })
面试重点是说清编码格式、服务端解析方式、是否触发 CORS 预检、是否适合文件上传,以及浏览器如何自动处理 multipart boundary。
完整版教学
一、Content-Type 是请求体说明书
服务端收到请求体时,需要知道这串字节应该按什么格式解析。Content-Type 就是请求体的媒体类型说明,前端设置错了,服务端可能读不到参数或解析失败。
| 类型 | 格式 | 常见场景 |
|---|---|---|
application/json | JSON 字符串 | 现代 API |
application/x-www-form-urlencoded | a=1&b=2 | 传统表单 |
multipart/form-data | 多段边界分隔 | 文件上传 |
text/plain | 纯文本 | 简单文本上报 |
记忆钩子:JSON 管结构,urlencoded 管简单键值,multipart 管文件和混合字段。
二、JSON 请求的特点
fetch('/api/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 1, items: [2, 3] }),
})
JSON 表达嵌套对象和数组很自然,前后端接口最常用。代价是跨域时 application/json 不属于简单请求的简单 Content-Type,通常会触发 OPTIONS 预检。
假设请求从 app.example.com 发到 api.example.com,POST JSON 往往会先出现一次 OPTIONS,再出现真实 POST。这个现象不是多发了业务请求,而是浏览器安全检查。
三、urlencoded 的特点
const body = new URLSearchParams({ name: 'Tom', age: '18' })
fetch('/api/form', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
body,
})
urlencoded 适合简单键值,浏览器传统表单默认就常用它。它不擅长表达复杂嵌套结构,不同后端对数组和对象的解析约定也可能不同,例如 ids=1&ids=2 或 ids[]=1&ids[]=2。
四、FormData 和 multipart
const form = new FormData()
form.append('file', file)
form.append('desc', 'avatar')
fetch('/upload', {
method: 'POST',
body: form,
})
multipart/form-data 会把每个字段分成一段,文件字段可以带文件名和类型。浏览器会自动生成类似 boundary=----WebKitFormBoundary... 的分隔符。
不要手动设置:
headers: { 'Content-Type': 'multipart/form-data' } // 常见错误
如果手动写了但没有 boundary,服务端无法知道各段如何分隔,上传解析就可能失败。
五、CORS 简单请求与预检
跨域请求是否预检,和方法、头部、Content-Type 都有关。简单 Content-Type 只包括有限几种,例如 application/x-www-form-urlencoded、multipart/form-data、text/plain。
| 请求 | 是否常触发预检 |
|---|---|
| POST + JSON | 是 |
| POST + urlencoded | 通常否 |
| POST + multipart + 自定义头 | 可能是 |
| 带 Authorization 头 | 是 |
为了避免预检而把所有接口改成 text/plain 通常不是好设计。预检是安全机制,优化要从缓存 Access-Control-Max-Age、减少不必要自定义头、合并请求等方向考虑。
六、服务端解析和安全边界
前端发什么 Content-Type,服务端就要启用对应解析器。JSON 解析器、表单解析器、文件上传中间件处理方式不同,大小限制也不同。
请求 -> 根据 Content-Type 选择 parser -> 得到 body/files -> 业务校验
文件上传尤其要限制大小、类型和数量。前端可以做格式提示,但不能替代服务端校验;攻击者可以直接构造请求绕过前端限制。
七、常见误区与追问
- 误区:所有 POST 都应该用 JSON。 文件上传和传统表单有更合适的编码方式。
- 误区:FormData 要手动设置 Content-Type。 浏览器会自动设置 multipart boundary,手动设置反而容易错。
- 误区:预检请求是接口重复调用。 OPTIONS 是浏览器的跨域许可检查,不是业务提交。
- 追问:为什么 JSON 跨域常有预检?
application/json不属于简单 Content-Type。 - 追问:urlencoded 能传复杂对象吗? 可以约定编码,但不同后端解析规则不统一。
- 追问:前端限制文件类型安全吗? 只能改善体验,服务端必须重新校验。
八、加强记忆
请求体类型按“结构、表单、文件”记:结构化数据用 JSON,简单表单用 urlencoded,文件和混合字段用 FormData/multipart。答题时一定补上两个边界:FormData 不手写 Content-Type,JSON 跨域常触发预检。Content-Type 不是随便填的字符串,而是前后端解析协议。