直奔主題,就不墨跡了,檔案資料是post資料,html表單裡面post支援三種enctype,
檔案上傳的那種是multipart/form-data。做來做個試驗。const http = require('http');
const fs = require('fs');
let server = http.createServer((req,res) => {
let str = '';
req.on('data',(data) => {
str+=data;
});
req.on('end',() => {
console.log(str);
res.end();
})
})
server.listen(8080, () => {
console.log(`伺服器已啟動,監聽在8080埠`);
});
複製程式碼
html
<form action="http://localhost:8080" enctype="multipart/form-data" method="post">
<input type="text" name="user" id="">
<input type="password" name="pass" id="">
<input type="file" name="f1" id="">
<input type="submit" value="提交">
</form>
複製程式碼
得到的結果是這樣的
這裡的html檔案裡的內容沒有都截出來。所以看起來有點奇怪,我們來分析一下這段資料,顯然------WebKitFormBoundaryDqs4grRjEIPy5ZmY這個東東是分隔符。把這個東西稱為分隔符沒毛病吧,然後資料就變成了分隔符
Content-Disposition: form-data; name="user"
chen
分隔符
Content-Disposition: form-data; name="pass"
123321`1
分隔符
Content-Disposition: form-data; name="f1"; filename="test.html"
Content-Type: text/html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
複製程式碼
然後把換行換成\r\n,資料就變成了這樣
<分隔符>\r\n
Content-Disposition: form-data; name="user"\r\n
\r\n
chen\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="pass"\r\n
\r\n
123321`1\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="f1"; filename="test.html"\r\n
Content-Type: text/html\r\n
\r\n
<檔案內容>\r\n
<分隔符>--
複製程式碼
然後把Content-Disposition: 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
<分隔符>--
複製程式碼
這樣下來,就方便解析資料了,把他們先用分隔符切一下,就變成了,
空,
\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
]
這裡的空,可能有些疑惑,不過你反應5秒鐘應該就不疑惑了。空和最後一個資料沒有價值,
於是我們把他們去掉,於是資料就變成了這樣
[
\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,把這個也去掉,資料就變成了這樣
資料描述\r\n\r\n資料值,
資料描述\r\n\r\n資料值,
資料描述1\r\n資料描述2\r\n\r\n<檔案內容>,
]
複製程式碼
然後再用\r\n\r\n對資料在切一次,就變成了這樣
普通資料:[資料描述, 資料值]
或
檔案資料:[資料描述1\r\n資料描述2, <檔案內容>]
複製程式碼
到這就可以動手寫程式碼了,
const http=require('http');
const fs=require('fs');
const uuid=require('uuid/v4');
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;
};
let server=http.createServer((req, res)=>{
let arr=[];
req.on('data', data=>{
arr.push(data);
});
req.on('end', ()=>{
let data=Buffer.concat(arr);
//data
//解析二進位制檔案上傳資料
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(buffer=>buffer.slice(2,buffer.length-2));
//4.每個資料在第一個"\r\n\r\n"處切成兩半
arr.forEach(buffer=>{
let n=buffer.indexOf('\r\n\r\n');
let disposition=buffer.slice(0, n);
let content=buffer.slice(n+4);
disposition=disposition.toString();
if(disposition.indexOf('\r\n')==-1){
//普通資料
//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{
//檔案資料
/*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
Content-Type: text/plain*/
let [line1, line2]=disposition.split('\r\n');
let [,name,filename]=line1.split('; ');
let type=line2.split(': ')[1];
name=name.split('=')[1];
name=name.substring(1,name.length-1);
filename=filename.split('=')[1];
filename=filename.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);
}
});
}
});
//5.完成
console.log(post);
}
}
res.end();
});
});
server.listen(8080,() => {
console.log('伺服器啟動在8080埠);
})
複製程式碼
到這就可以測試了。
靈感來源blue課堂。