Node解析POST資料
原理
表單的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);
輸出: