原理
表单的POST提交主要有三种数据格式:
- text/plain
- application/x-www-form-urlencoded
- multipart/form-data
其中,text/plain
用的很少;application/x-www-form-urlencoded
是默认,采用url编码方式,以xxx=xxx&xxx=xx...
格式传输数据;multipart/form-data
用于上传文件内容。
当表单传输数据到服务端后,服务端会接受如下数据
上面的数据格式可以简化为:
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n
<分隔符>–
实现思路
因此,服务端可以通过以下步骤来解析数据:
1、通过”<分隔符>“将数据进行切分
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n,
--\r\n]
2、丢弃头尾元素
[
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n
]
3、丢弃每一项头尾的\r\n
[
数据描述\r\n\r\n数据值,
数据描述\r\n\r\n数据值,
数据描述1\r\n数据描述2\r\n\r\n<文件内容>
]
4、用第一次出现的\r\n\r\n将数据进行切分
普通数据:[数据描述, 数据值]
或
文件数据:[数据描述1\r\n数据描述2, <文件内容>]
5、判断描述的里面有没有"\r\n"
有——文件数据:[数据描述1\r\n数据描述2, <文件内容>]
没有——普通数据:[数据描述, 数据值]
实例
获取分隔符
分隔符是一组随机字符串,如何获取分隔符?可以通过req.headers['content-type']
获取
API
1.查找 indexOf()
2.截取 slice(s, e)
3.切分 split
由于Buffer不提供split方法,因此需要我们自己实现:
Buffer.prototype.split=Buffer.prototype.split||function (b){
let arr=[];
let cur=0;
let n=0;
while((n=this.indexOf(b, cur))!=-1){
arr.push(this.slice(cur, n));
cur=n+b.length;
}
arr.push(this.slice(cur));
return arr;
};
完整代码
const http=require('http');
const common=require('./libs/common'); //split 方法
const fs=require('fs');
const uuid=require('uuid/v4');
let server=http.createServer((req, res)=>{
let arr = [];
req.on('data', data => {
arr.push(data);
});
req.on('end', () => {
let data = Buffer.concat(arr);
let post = {}; //普通数据
let files = {}; // 文本数据
if (req.headers['content-type']) {
let str = req.headers['content-type'].split(';')[1];
if(str) {
let boundary = '--' + str.split('=')[1];
// 1、通过<分隔符>将数据进行切分
let arr = data.split(boundary);
// 2、丢弃头尾元素
arr.shift();
arr.pop();
// 3、丢弃每一项头尾的\r\n
arr = arr.map((elem) => elem.slice(2, elem.length-2));
// 4、用第一次出现的\r\n\r\n将数据进行切分
arr.forEach(elem => {
let n = elem.indexOf('\r\n\r\n');
let disposition = elem.slice(0, n);
let content = elem.slice(n+4);
disposition = disposition.toString();
//5、判断描述的里面有没有"\r\n"
if(disposition.indexOf('\r\n') == -1) {
// console.log('普通数据');
//Content-Disposition: form-data; name="user"
content = content.toString();
let name = disposition.split(';')[1].split('=')[1];
name = name.substring(1, name.length-1);
post[name] = content;
}else {
// console.log('文件数据');
/*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
Content-Type: text/plain*/
let [,name,filename] = disposition.split('\r\n')[0].split(';');
let type = disposition.split('\r\n')[1].split(':')[1];
name = name.split('=')[1].substring(1,name.length-1);
filename = filename.split('=')[1].substring(1,filename.length-1);
let path = `upload/${uuid().replace(/\-/g, '')}`;
fs.writeFile(path, content, err=>{
if(err){
console.log('文件写入失败', err);
}else{
files[name]={filename, path, type};
console.log(files);
}
});
}
})
console.log(post);
}
}
res.end();
})
});
server.listen(1337);
输出: