本文主要介绍form数据的格式以及如何用代码手动构造一个请求。
一、常用的form enctype类型:
-
application/x-www-form-urlencoded
form默认的编码方式,会编码特殊文字,也是jq等库ajax提交数据默认的编码格式
-
multi/formdata
这种方式可以提交文本+二进制格式
-
text/plain
只编码空格符,这种方式不太常用
其中比较复杂的是multi/formdata类型的,可混合提交文本数据与二进制数据(图片、zip等),下面主要介绍一下这种编码的规律以及在js以及nodejs的构造的方法。
二、分析一个multi/formdata的例子
以下是一个formdata的格式(chrome中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
------WebKitFormBoundary0yB3cIYoABZUBzEm Content-Disposition: form-data; name="user"
aotu.io ------WebKitFormBoundary0yB3cIYoABZUBzEm Content-Disposition: form-data; name="psw"
123456 ------WebKitFormBoundary0yB3cIYoABZUBzEm Content-Disposition: form-data; name="onefile"; filename="onefile-1460812719250.png" Content-Type: image/png
.... (png binary data) .... ------WebKitFormBoundary0yB3cIYoABZUBzEm--
|
分析一下这种编码格式的特点:
1、request header里声明Content-Type,并且在其后加上数据分界符:boundary,即:Content-Type:multipart/form-data; boundary=—-WebKitFormBoundary0yB3cIYoABZUBzEm。
boundary的字符应该是随机的,防止提交的数据里有相同字符而影响服务器的数据解析。
2、request body的部分,规律可看下面的图解:
可以看出:
-
body里的boundary总比Content-Type声明的boundary前面多两个中划线;
-
而body结束部分的boundary则在后面再加上两个中划线;
-
每行后面都应该有一个换行符『\r\n』,field name行与field值行之间还有一行仅有一个换行符的空白行。
三、如何构造
以上介绍完了multi/formdata的编码结构,下面说说在js里面是怎么构造的这样的一个请求的。
有同学问了,为啥不用new FormData()直接构造数据呢?嗯。。。
请看以下代码以及注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
<form class="J_form"> <p>用户名:<input type="text" name="user" value="aotu.io" readonly></p> <p>密码:<input type="text" name="psw" value="123456" readonly></p> <p><label for="onefile">png图片:</label><input type="file" name="onefile" id="onefile" class="J_file"></p> <p><input type="button" value="提交" class="J_submit" title="请选择文件后再提交" style="font-size:14px;" disabled><span style="font-size:12px;color:#999;">(请选择文件后再提交)</span></p> <p>返回结果:<span class="J_ret"></span></p> <p><img class="J_img" src=""></p> </form> <script> var picBuffer = null; document.querySelector('.J_file').onchange = function(evt) { var fileReader = new FileReader(); var file = evt.target.files[0]; document.querySelector('.J_submit').removeAttribute('disabled'); fileReader.onload = function(e) { picBuffer = e.target.result; } fileReader.readAsArrayBuffer(file); };
document.querySelector('.J_submit').onclick = function() { var boundary_key = 'aotu_lab'; var boundary = '--' + boundary_key; var end_boundary = '\r\n' + boundary + '--'; var retsult = ''; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="user"' + '\r\n\r\n'; retsult += document.querySelector('input[name=user]').value + '\r\n'; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="psw"' + '\r\n\r\n'; retsult += document.querySelector('input[name=psw]').value + '\r\n'; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="onefile"; filename="pic.png"' + '\r\n'; retsult += 'Content-Type: image/png' + '\r\n\r\n'; var resultArray = []; for (var i = 0; i < retsult.length; i++) { resultArray.push(retsult.charCodeAt(i)); } var pic_typedArray = new Uint8Array(picBuffer); var endBoundaryArray = []; for (var i = 0; i < end_boundary.length; i++) { endBoundaryArray.push(end_boundary.charCodeAt(i)); } var postArray = resultArray.concat(Array.prototype.slice.call(pic_typedArray), endBoundaryArray); var post_typedArray = new Uint8Array(postArray); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { } }; xhr.open("POST", "/submit"); xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary='+boundary_key); xhr.send(post_typedArray.buffer); }; </script>
|
在上面的代码里,由于暂时没能在官方文档找到拷贝或concat整段buffer的方法,所以合并字符数据与图片数据的方法并不十分高效,需要转为普通数组,合并数据后再次转为typed array以获得buffer数据。经过一番转换后计算效率在大文件下或者手机端这种方法的效率还待验证,因为处理普通数组会比直接操作二进制慢不少。
注意,以上代码并不兼容发送中文的情况,大概就是思路用合适字节长度去存储经过encodeURIComponent(或charCode)后的字符,字符code与二进制编码之间的转换这不在本文的讨论范围,有兴趣的同学可参考文后的参考资料。
FormData的api使我们可以处理blob(buffer)、text等数据的提交,平常开发已足够。但是通过了解formdata的数据底层组装方式,或许有助于我们在浏览器端更灵活的处理一些二进制数据、提供一些新思路。
最后再附上nodejs版的构造方式的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
var boundary_key = 'aotu_lab'; var boundary = '--' + boundary_key; var end_boundary = boundary + '--'; var request = http.request({ host: '127.0.0.1', port: 3000, path: '/submit', method: 'post' }, function(req) { req.on('data', function(buf) { console.log('response from node:'); console.log(buf.toString()); }); }); request.setHeader('Content-Type', 'multipart/form-data; boundary='+ boundary_key); var retsult = ''; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="user"' + '\r\n\r\n'; retsult += 'aotu.io' + '\r\n'; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="psw"' + '\r\n\r\n'; retsult += '123456' + '\r\n'; retsult += boundary + '\r\n'; retsult += 'Content-Disposition: form-data; name="onefile"; filename="pic.png"' + '\r\n'; retsult += 'Content-Type: image/png' + '\r\n\r\n'; request.write(retsult);
var picStream = fs.createReadStream('./public/images/o2logo.png'); picStream.on('end', function() { request.write('\r\n' + end_boundary); request.end(); res.end('post form data success!'); }) .pipe(request, {end: false});
|
有兴趣的同学可以clone这个git地址获取代码:
https://github.com/cos2004/formrequest_app.git
代码介绍:
首先npm install,然后npm start启动服务器
http://127.0.0.1:3000/
,可演示js版的提交,请选择png格式文件
http://127.0.0.1:3000/formdata
演示nodejs版提交,在terminal可以看到输出结果
http://127.0.0.1:3000/submit
表单数据提交接口
参考资料
W3C关于Forms的说明
字符编码笔记:ASCII,Unicode和UTF-8
MDN docs
你所不知道的JavaScript数组
如何用nodejs通过post发送multipart/form-data类型的http请求
上次更新:2019-04-20 19:28:31