本文介绍的是通过html的form标签实现文件上传功能,是一个比较简易的文件上传实例,分享一下学习成果。
客户端部分
要上传文件必须将表单enctype设置为multipart/form-data,这个参数表示表单将会以多部件表单的形式上传
enctype的默认值是enctype=”application/x-www-form-urlencoded”。这个值的意思指将会对表单项的内容进行url编码,所谓url编码就将请求参数转换为二进制编码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data">
用户名:<input type="text" name="user"><br>
密码:<input type="password" name="pass"><br>
<input type="file" name="f1"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
服务器端部分
multipart数据:
------WebKitFormBoundaryHQletlTVegg2bril
Content-Disposition: form-data; name="user"
1213
------WebKitFormBoundaryHQletlTVegg2bril
Content-Disposition: form-data; name="pass"
1234
------WebKitFormBoundaryHQletlTVegg2bril
Content-Disposition: form-data; name="f1"; filename="1.txt"
Content-Type: text/plain
aa
bbb
ccccc
------WebKitFormBoundaryHQletlTVegg2bril--
—————————————————————————————————————————————————————————————————————————————————————————
整理为如下形式:
<分隔符>\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,
--
]
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,文件内容]
没有——普通数据:[数据描述,数据值]
6. 分析“数据描述”
Buffer对象的分割见 https://blog.youkuaiyun.com/Seven_miao/article/details/86090683
const http=require('http')
const fs=require('fs')
const common=require('./libs/common')
const uuid=require('uuid')
const zlib=require('zlib')
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={}
console.log(bound)
// data
// 解析二进制文件上传数据
// 获取分隔符
//content-type:multipart/form-data; boundary=----WebKitFormBoundaryPrAAAnu8AfAbfCwj
if(req.headers['content-type']){
let str=req.headers['content-type'].split('; ')[1]
if(str){
//获取boundary
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=content.toString();
let name=disposition.split('; ')[1].split('=')[1];
name=name.slice(1,name.length-1)
post[name]=content;
}else{
// 文件数据
let [line1,line2]=disposition.split('\r\n');
//line1:Content-Disposition: form-data; name="f1"; filename="1.txt"
//line2:Content-Type: text/plain
let [,name,filename]=line1.split('; ');
name=name.split('=')[1]
name=name.slice(1,name.length-1);
filename=filename.split('=')[1]
filename=filename.slice(1,filename.length-1);
let type=line2.split(': ')[1]
let path=`upload/${uuid().replace(/\-/g,'')}`;
let rs=fs.createReadStream(filename);
let ws=fs.createWriteStream(path);
let gzip=zlib.createGzip();
rs.pipe(gzip).pipe(ws);
rs.on('error',err=>{
res.write('写入失败');
console.log(err);
res.end();
});
ws.on('finish',()=>{
console.log('写入成功')
})
}
})
console.log(post)
}
}
res.end()
})
});
server.listen(8080)