最近在研究如何使用 fetch 方法发送表单请求,因为我们一般使用的时候fetch一般会将发送的数据处理成 JSON 字符串的格式进行发送,但是如果提交表单的话由于表单格式需要特殊处理,因为无法直接使用 fetch
进行发送,这里介绍一下如何处理可以使用 fetch 发送表单数据。
以一个表单作为示例,下图的HTTP请求将向本地发送一个表单提交的请求,请求表单内容包含
- name : 张三
- age : 23
- sex : man
- file : 简历.pdf
上图就是我们使用表单提交请求时,浏览器将我们的表单数据处理后的大致结果,首先会在内容类型(Cpontent-Type)设置为multiparty/form-data
类型,然后设置了不同data之间的边界值,然后又边界开始对每个表单字段数据进行格式化处理。这样的格式直接调用fetch
方法是得不到的,因为我们需要借助浏览器中的FormData
对象进行处理:
form = document.createElement("form");
form.innerHTML = "<input name='name' value='张三' type='text'/>"
+"<input name='age' value='23' type='text'/>"
+"<input name='sex' type='male'/>"
+"<input name='file' type='file'/>"
+"<input name='submit' type='submit'/>";
form.onsubmit = function(){
data = new FormData(this); // 将表单节点数据封装为 FormData 格式
fetch("http://localhost:7001/form/submit",{
headers:{
// 注意这里不要设置 Content-Type 请求头,否则会导致错误
},
method:"POST",
Origin:window.location.protocol+"//"+window.location.host,
// fetch 的 body 发送 data
body:data
}).then(res=>res.json()).then(json=>console.log(json));
return false;
}
document.getElementsByClassName("body")[0].append(form);
上面的代码在执行后,会在页面最后增加一个表单元素,当点击提交的时候就会调用 onsubmit
中定义的方法发起 fetch 请求,并且将相应结果打印返回。
对于想要自定义的字段,也可以使用 FormData
对象中的方法进行追加,具体使用方式参考文档,下面为示例:
form = document.createElement("form");
form.innerHTML = "<input name='file' type='file'/><input name='submit' type='submit'/>";
form.onsubmit = function(){
data = new FormData(this); // 将表单节点数据封装为 FormData 格式
data.append("name","张三");
data.append("age","23");
data.append("sex","male");
fetch("http://localhost:7001/form/submit",{
headers:{
// 注意这里不要设置 Content-Type 请求头,否则会导致错误
},
method:"POST",
Origin:window.location.protocol+"//"+window.location.host,
// fetch 的 body 发送 data
body:data
}).then(res=>res.json()).then(json=>console.log(json));
return false;
}
document.getElementsByClassName("body")[0].append(form);
如果我们想要自定义字段且上传文件,我们可以利用 <input type=‘file’>
标签中的 files
属性,需要提交时向对应字段中上传文件
document.body.innerHTML = "<input id="form_file" type='file'/>";
form.onsubmit = function(){
data = new FormData();
// 添加 file 属性的表单字段
data.append("file",document.getElementById("form_file").files[0]);
fetch("http://localhost:7001/form/submit",{
headers:{
// 注意这里不要设置 Content-Type 请求头,否则会导致错误
},
method:"POST",
Origin:window.location.protocol+"//"+window.location.host,
// fetch 的 body 发送 data
body:data
}).then(res=>res.json()).then(json=>console.log(json));
return false;
}
document.getElementsByClassName("body")[0].append(form);
关于 the request was rejected because no multipart boundary was found
异常
这是我在调试过程中遇到一个异常,通过 google
发现这个异常是由于未获取到表单数据边界导致的。
通过检查代码发现fetch之所以没有给我带上表单数据边界的原因是由于我手动定义了 Content-Type
导致的,如下:
fetch("http://localhost:7001/form/submit",{
headers:{
"Content-Type":"multipart/form-data"
},
method:"POST",
Origin:window.location.protocol+"//"+window.location.host,
// fetch 的 body 发送 data
body:data
}).then(res=>res.json()).then(json=>console.log(json));
有发现少了什么吗,当设置了 Content-Type
后,对比之前的请求就会发现少了后面的表单数据边界部分的定义。这是因为由于我们手动设置了 Content-Type
之后,浏览器边不再会为我们自动设置Content-Type
了,发送的请求不是一个规范的表单请求,服务器无法正确解析而导致报错。
解决的方式也很简单,移除对 Content-Type
的设置即可。
关于 Content-Type: application/x-www-form-urlencoded
在研究过程中发现了另外一种请求的数据类型,因为类型名称中带有 form
所以一直以为和表单类型的请求有什么关系,经过一番研究之后发现这种类型的请求只是将请求地址中的数据移到了请求体中而已,如下:
POST http://localhost:7001/user?id=1&name=zhangsan&age=23
实际上和下面这种格式等价:
POST http://localhost:7001/user
Content-Type: application/x-www-form-urlencoded
id=1&name=zhangsan&age=23
这种格式有一个好处,就是请求头的长度实际上是有长度限制的,通过这种处理可以大大增加请求数据的长度。发起的 fetch
是这样的:
fetch("http://localhost:7001/user",{
headers:{
"Content-Type":"application/x-www-form-urlencoded"
},
method:"POST",
Origin:window.location.protocol+"//"+window.location.host,
// fetch 的 body 发送 data
body:"id=1&name=zhangsan&age=23"
}).then(res=>res.json()).then(json=>console.log(json));
有感兴趣的童鞋可以深入研究下。
参考资料
form表单提交、ajax、fetch、formdata,以及跨域
fetch使用formdata数据格式发送请求
fetch 使用 form data 方式提交
HTTP请求中的form data和request payload的区别
React基于fetch的表单提交
FileUploadException1
FileUploadException2
FormData 对象的使用
使用 Fetch
jquery ajax三种方式异步提交表单