Description
背景
最近负责的一个项目里(接口请求采用fetch
)涉及文件上传表单,参考另外一个项目(接口请求采用axios)的操作方式,顺理成章写好了(Ctrl+C/V
):
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
return fetch('https://example.com', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
结果上传失败,后端童鞋说请求有问题(没有Boundary)。其他项目好好地,为啥我这个项目就不行(惊悚)。
先检查了下请求:
content-type
里确实没有boundary
。
解决问题
删除手动设置的Content-Type
就行了:
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
return fetch('https://example.com', {
method: 'POST',
body: formData
})
}
观察下此时的请求(content-type
里有boundary
):
content-type: multipart/form-data; boundary=----WebKitFormBoundaryaUiVhiUXfFmuYUEc
解惑
1. 什么是FormData
boundary?
得先看看Form Content-Type
的值之一multipart/form-data
的功能。
The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters.
所以才引入了multipart/form-data
,而bounday
在作为各个part
(对应表单的各个字段)之间的分隔符。
const formData = new FormData();
// Form有三个字段
formData.append('file', file);
formData.append('name', 'john');
formData.append('age', 22);
return fetch(
'https://example.com',
{
method: 'POST',
body: formData
}
)
看看请求数据:
------WebKitFormBoundaryy7AV3mXGZkpXR6dr
Content-Disposition: form-data; name="file"; filename="air.detail.png"
Content-Type: image/png
------WebKitFormBoundaryy7AV3mXGZkpXR6dr
Content-Disposition: form-data; name="name"
john
------WebKitFormBoundaryy7AV3mXGZkpXR6dr
Content-Disposition: form-data; name="age"
22
------WebKitFormBoundaryy7AV3mXGZkpXR6dr--
顺便看看请求头:
content-type: multipart/form-data; boundary=----WebKitFormBoundaryy7AV3mXGZkpXR6dr
三个表单字段对应三个part
。关于每个part
的构成可以看下规范。文件上传对应的part
会多个filename
,并且内容是字节流也不会展示在里面。
正因为boundary
浏览器控制台面板里可以看到格式化后的FormData
数据:
2. 为什么需要删除手动设置的Content-Type
?
Disappointing but true.
浏览器会自动设置'Content-Type': 'multipart/form-data'
,并且计算bundary
。手动设置'Content-Type': 'multipart/form-data'
时,浏览器就不会自动处理了。
3. 为什么axios可以手动设置Content-Type
?
axios是基于Ajax的,难道是Ajax可以手动设置Content-Type
?试着把上面的代码换成Ajax也是不能上传成功,说明Ajax上传文件时也不能手动设置Content-Type
!看来axios有点“智能”。撸下axios代码发现了原因:
axios/lib/adapters/xhr.js
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
再看看isFormData
的实现:
function isFormData(val) {
return (typeof FormData !== 'undefined') && (val instanceof FormData);
}
原来axios会内部会自动删除FormData
的手动设置的Content-Type
。
总结:
- 无论是
Ajax
还是fetch
,在使用FormData
传输数据时,都不能手动设置content-type
; - axios会内部会自动删除
FormData
的手动设置的Content-Type
。
参考
- WebAPI: fetch
- Uploading files using 'fetch' and 'FormData'
- 踩坑篇--使用 fetch 上传文件
- fetch - Missing boundary in multipart/form-data POST
扩展: