使用nodejs实现文件上传比较麻烦,本文只是方便理解原理简单实现
1.准备html页面
<!DOCTYPE html>
<html lang="zh-CN">
<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">
<title>Document</title>
</head>
<body>
<form action="http://localhost:8080" enctype="multipart/form-data">
姓名:<input type="text" name="username">
<br />
密码:<input type="password" name="password">
<br />
文件:<input type="file" name='file'>
<br />
<input type="submit" value="提交" name="">
</form>
</body>
</html>
2.编写nodejs代码接收前台数据
const http = require('http');
http.createServer((req,res)=>{
let arr = [];
req.on('data',buffer=>{
arr.push(buffer);
})
req.on('end',()=>{
console.log(arr.toString());
})
}).listen(8080);
这里我们为了方便查看提交的数据,我们将buffer数据转换为普通字符串
当提交时结果如下‘
可以看到数据被分隔符(-----WebKit…)分隔成了好几段
第一段:用户名
第二段:密码
第三段:文件内容
文件内容多了一行文件类型说明
数据获取成功,但我们怎么将这些数据格式化 如(username:dfdf,password:dedse....)
的形式在不借助第三方模块的情况下。
通过观察我们发现,他的数据结构大概是这样
<分隔符>\r\n字段信息\r\n\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.
[
null,
\r\n字段信息\r\n\r\n内容\r\n,
\r\n字段信息\r\n\r\n内容\r\n,
\r\n\字段信息\r\n文件类型\r\n\r\n内容\r\n,
--
]
2.此时在将第0个和最后一个去掉得到
[
\r\n字段信息\r\n\r\n内容\r\n,
\r\n字段信息\r\n\r\n内容\r\n,
\r\n\字段信息\r\n文件类型\r\n\r\n内容\r\n,
]
3.在将数组中的每个元素的第一个换行和最后一个换行去掉得到如下
[
字段信息\r\n\r\n内容,
字段信息\r\n\r\n内容,
字段信息\r\n文件类型\r\n\r\n内容,
]
4.对于普通数据我们在通过字符串切割,切掉\r\n\r\n即可
字段信息,内容
字段信息,内容
...
思路大致明了,但前台提交的是buffer数据,我们不能直接将其转换为字符串在通过字符串的方法进行切割。
不过好在Buffer为我们提供了一些方法,这些方法与字符串的方法一样。buffer.indexOf(str)
:查找str在buffer数据中的位置buffer.slice(start,end)
类似于js中的substring
注意:buffer中没有切分数据的方法
通过以上两个方法我们就能实现buffer数据的切分
如下示例
let buffer = new Buffer('abc\r\nbsdk\r\nrgg');//定义一个buffer数据,使用分隔符隔开
function bufferSplit(buffer,deli){
let arr = [];//存储最终数据
let n = 0;//分隔符开始的位置
//如果分隔符存在
while((n=buffer.indexOf(deli))!=-1){
arr.push(buffer.slice(0,n));//abc
buffer=buffer.slice(n+deli.length);
//第一次buffer=bsdk\r\n\rgg
//第二次buffer=\r\nrgg
..
}
arr.push(buffer);
return arr;
}
console.log(bufferSpli(buffer,'\r\n').toString());
//返回 abc,bsdk,rgg
我们通过上面的函数加简单的分隔了buffer数据,现在我们将他应用到文件上传项目上
将这个函数定义成一个模块
exports.bufferSplit=function(buffer,deli){
let arr = [];
let n = 0;
while((n=buffer.indexOf(deli))!=-1){
arr.push(buffer.slice(0,n));
buffer=buffer.slice(n+deli.length);
}
arr.push(buffer);
return arr;
}
使用这个模块切割分隔符
上面我们所说的分隔符其实在 req.headers
中
const http = require('http');
const util = require('uti');
http.createServer((req,res)=>{
console.log(req.headers);
let arr = [];
req.on('data',buffer=>{
arr.push(buffer);
})
req.on('end',()=>{
let buffer = Buffer.concat(arr);
})
}).listen(8080);
需要注意的是headers里面的分隔符比我们最后得到的分隔符少两个‘-’,我们在获取分隔符的时候要注意加两个-
//最后得到:
------WebKitFormBoundary0qjsU7Tl6U1BApvt
//headers:
----WebKitFormBoundary0qjsU7Tl6U1BApvt
获取分隔符片段
let boundary='--'+req.headers['content-type'].split('; ')[1].split('=')[1];
console.log(boundary);
//------WebKitFormBoundaryn3lpgLIrRcqpTTWi
切割分隔符
const http = require('http');
const util = require('uti');
http.createServer((req,res)=>{
// console.log(req.headers);
let boundary='--'+req.headers['content-type'].split('; ')[1].split('=')[1];
// console.log(boundary);
let arr = [];
req.on('data',buffer=>{
arr.push(buffer);
})
req.on('end',()=>{
let buffer = Buffer.concat(arr);
//切割分隔符
let result = util.bufferSplit(buffer,boundary);
console.log(result.toString());
})
}).listen(8080);
数据和我们料想的一样
[
null,
\r\n字段信息\r\n\r\n内容\r\n,
\r\n字段信息\r\n\r\n内容\r\n,
\r\n\字段信息\r\n文件类型\r\n\r\n内容\r\n,
--
]
删除第一行和最后一个元素‘
req.on('end',()=>{
let buffer = Buffer.concat(arr);
//切割分隔符
let result = util.bufferSplit(buffer,boundary);
//删除第一个和最后一个元素
result.pop();
result.shift();
console.log(result.toString());
})
对数组中的每一个元素处理
req.on('end',()=>{
let buffer = Buffer.concat(arr);
//切割分隔符
let result = util.bufferSplit(buffer,boundary);
result.pop();
result.shift();
// console.log(result.toString());
//对每一个元素进行处理
result.forEach(buffer=>{
buffer = buffer.slice(2,buffer.length-2);//切掉buffer前面和后面的换行
let n = buffer.indexOf('\r\n\r\n');//获取分分隔符的位置
let info = buffer.slice(0, n).toString();//获取字段信息
let data = buffer.slice(n+4);//获取字段内容
console.log(info);
console.log(data.toString());
})
})
打印结构如下
判断文件数据和普通数据获取字段名/文件名
req.on('end',()=>{
let buffer = Buffer.concat(arr);
//1.切割分隔符
let result = util.bufferSplit(buffer,boundary);
result.pop();
result.shift();
// console.log(result.toString());
//2.对每一个元素进行处理
result.forEach(buffer=>{
//3.切断buffer前面和后面的换行
buffer = buffer.slice(2,buffer.length-2);
//4.去掉每个元素的\r\n\r\n
let n = buffer.indexOf('\r\n\r\n');//获取分分隔符的位置
let info = buffer.slice(0, n).toString();//获取字段信息
let data = buffer.slice(n+4);//获取字段内容
// console.log(info);
// console.log(data.toString());
//5.判断文件和普通数据
if(info.indexOf('\r\n')!=-1){
//5.1文件信息 获取字段名和文件名
let res2 = info.split('\r\n')[0].split('; ');
let name = res2[1].split('=')[1];//获取字段名
let filename = res2[2].split('=')[1];//获取文件名
name = name.substring(1, name.length-1);//去掉引号
filename = filename.substring(1, filename.length-1);
console.log(name);
console.log(filename);
}else{
//5.2普通信息 获取字段名
let name = info.split('; ')[1].split('=')[1];
name = name.substring(1, name.length-1);
console.log(name+':'+data);
//username:admin
//password:23456754
}
})
})
通过fs模块实现文件上传
req.on('end',()=>{
let buffer = Buffer.concat(arr);
//1.切割分隔符
let result = util.bufferSplit(buffer,boundary);
result.pop();
result.shift();
// console.log(result.toString());
//2.对每一个元素进行处理
result.forEach(buffer=>{
//3.切断buffer前面和后面的换行
buffer = buffer.slice(2,buffer.length-2);
//4.去掉每个元素的\r\n\r\n
let n = buffer.indexOf('\r\n\r\n');//获取分分隔符的位置
let info = buffer.slice(0, n).toString();//获取字段信息
let data = buffer.slice(n+4);//获取字段内容
// console.log(info);
// console.log(data.toString());
//5.判断文件和普通数据
if(info.indexOf('\r\n')!=-1){
//5.1文件信息 获取字段名和文件名
let res2 = info.split('\r\n')[0].split('; ');
let name = res2[1].split('=')[1];//获取字段名
let filename = res2[2].split('=')[1];//获取文件名
name = name.substring(1, name.length-1);//去掉引号
filename = filename.substring(1, filename.length-1);
// console.log(name);
// console.log(filename);
//文件上传
fs.writeFile(`upload/${filename}`,data,err=>{
if(err){
console.log(err);
}else{
console.log('上传成功');
}
});
}else{
//5.2普通信息 获取字段名
let name = info.split('; ')[1].split('=')[1];
name = name.substring(1, name.length-1);
console.log(name+':'+data);
}
})
})
成功上传文件
到这里使用nodejs原生简单实现文件上传已经完成,但实在是太麻烦了,且还有很多没完善的地方,本文只为理解原理。
在我们真正做文件上传时可以借助第三方模块来完成
如multiparty模块
官方网址:https://www.npmjs.com/package/multiparty
通过multiparty实现文件上传
const http = require('http');
const multiparty = require('multiparty');
http.createServer((req,res)=>{
let form = new multiparty.Form({
uploadDir:'./upload'//指定上传的文件路径
});
form.parse(req);
//接收普通数据
form.on('field',(name,value)=>{
// name:字段名
// value:值
console.log('数据:',name,value);
})
//接收文件数据
form.on('file',(name,file)=>{
console.log('文件:',name,file);
})
//表单解析完成
form.on('close',()=>{
console.log('完成');
})
}).listen(8080);