POST请求,文件上传
server.js
const http = require('http');
let server = http.createServer((req,res)=>{
let arr=[];//使用数组接收data
req.on('data',(data)=>{
arr.push(data);
})
req.on('end',()=>{
//post传输过来的数据被分了很多块,需要自己进行解析
//将数组放到Buffer中、Buffer已经被放在node中,所以不需要引入模块
//转成Buffer是因为需要将数据保存在二进制状态下进行操作,否则会造成图片等数据损坏.
let data = Buffer.concat(arr);
console.log(data);
res.end();
})
})
server.listen(8087)
HTML
<form action="http://localhost:8087/aa" method="post" enctype="multipart/form-data">//enctype值看扩展【表单的三种POST】
<input type="text" name="user">
<input type="password" name="pass">
<input type="file" name="file">
<input type="submit" value="提交">
</form>
解析数据
//server.js中,将数据放到Buffer中之后,需要对Buffer进行解析,
//解析掉分隔符,\r\n,拆分数据描述与数据值.
//对Buffer进行操作所需函数(例:分隔符为`):
let b = new Buffer("`aaa`bbbb`bbcc");
b.indexOf("`")//返回第一个`所在的下标=>0,有第二个参数,index是指从第几个位置开始找.
b.slice(0,5);//包含开头,不包含结尾=>`aaa`
b.split("`");//暂时不支持该函数,这里自己动手写呗;
//如果Buffer中有split函数,就用split,如果没有就用自己的函数,参数b是分隔符.
Buffer.prototype.split=Buffer.prototype.split||function(b){
let arr = [];//接受数据用的数组
let cur = 0;//代表当前已经切分到哪个位置
let n = 0;//切分时用的结束下标
while((n=this.indexOf(b,cur))!=-1){//结束下标等于当前对象的第一个分隔符出现的位置,并且不等于-1.
arr.push(this.slice(cur,n));//将当前对象切分,从上次切分到的位置到当前找到的分隔符的位置.
cur = n+b.length;//更新当前的切分位置为,当前找到的分隔符位置加分隔符长度.
};
arr.push(this.slice(bur));//将最后一段添加到数组中;
return arr;
}
let arr = b.split("`");//[aaa,bbbb,bbcc];
//实际应用中POST传输过来的数据分隔符是随即的,被记录在req.hearders对象的content-type中,
//可以使用req.hearders[content-type]自己解析获得.
实际的server.js解析
const http = require('http');
const uuid = require('uuid/v4');
const fs = require('fs');
let server = http.createServer((req,res)=>{
let arr=[];//使用数组接收data
req.on('data',(data)=>{
arr.push(data);
})
req.on('end',()=>{
Buffer.prototype.split = Buffer.prototype.split||function(b){//自己动手写split函数
let arr = [];//接受数据用的数组
let cur = 0;//代表当前已经切分到哪个位置
let n = 0;//切分时用的结束下标
while((n=this.indexOf(b,cur))!=-1){//结束下标等于当前对象的第一个分隔符出现的位置,并且不等于-1.
arr.push(this.slice(cur,n));//将当前对象切分,从上次切分到的位置到当前找到的分隔符的位置.
cur = n+b.length;//更新当前的切分位置为,当前找到的分隔符位置加分隔符长度.
};
arr.push(this.slice(cur));//将最后一段添加到数组中;
return arr;
}
let post={};//用来放post请求的结果
let file={};//用来放文件
let data = Buffer.concat(arr);//将接收到的数组放到Buffer中
if(req.headers['content-type']){//判断是否有req.headers['content-type']
let head = '--'+req.headers['content-type'].split("; ")[1].split("=")[1];//切割得出分隔符
let arrs = data.split(head);//将二进制的Buffer通过分隔符进行初次分割
arrs.shift();//去掉数组第一个==》是个null
arrs.pop();//去掉数组最后一个==》是个--\r\n
arrs = arrs.map(buf=>buf.slice(2,buf.length-2));//使用map遍历,去掉数组中每个元素的开头两位与结束两位\r\n
arrs.forEach(element => {//对数组进行循环
let n =element.indexOf('\r\n\r\n');//找出数组元素中\r\n\r\n所在的位置
let disposition = element.slice(0,n);//得到数据描述
let content = element.slice(n+4);//得到数据值
disposition= disposition.toString();
if(disposition.indexOf('\r\n')==-1){//等于-1就是普通数据
content = content.toString();//将值转成字符串
let name = disposition.split('; ')[1].split('=')[1];//切割出参数中的name
name = name.substring(1,name.length-1);//切割出参数中的name
post[name] = content;//赋值给post
}else{//对文件进行操作
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)
console.log(name,filename,type);
console.log(content); //文件就是二进制,不需要转换成字符串,否则会造成写入失败
let path = `file/${uuid().replace(/\-/g,"")}`;
fs.writeFile(path,content,(err)=>{
if(err){
console.log('失败');
}else{
file[name] = {filename,path,type};
console.log(file);
}
});
}
});
console.log(post);
}
res.end();
})
})
server.listen(8087)
UUID(nodejs的第三方模块,需要使用npm下载)
cnpm i uuid -D;安装在当前文件夹下;
const uuid = require('uuid/v4');
console.log(uuid());
流操作、gz压缩
// 1. 上面的代码会等到全部数据都上传结束以后才开始处理.
// 2. readFile 会先把所有数据都读到内存在,然后再处理函数,这样非常占用内存,而且资源利用不充分.
// 解决如上问题,就需要使用流.
// 流操作是每次收到一部分就解析一部分.不再是全部读取之后才进行操作.
// 1. 读取流 fs.createRaedStream()、req
// 2. 写入流 fs.createWriteStream()、res
// 3. 读写流 压缩 const zlib = require('zlib'); zlib模块
// 例(文件流):
const fs = require('fs');
let ins = fs.createReadStream('1.png');//创建读取流
let outs = fs.createWriteStream('2.png');//创建写入流
ins.pipe(outs);//创建管道,连接两个流
ins.on('error',err=>{//读取流监听
console.log('读取失败处理方法')
})
ins.on('end',()=>{//读取流监听
console.log('读取完成方法')
})
outs.on('finish',()=>{//写入流监听
console.log('写入完成方法')
})
//例(网络流):
const http = require('http');
const fs = require('fs');
let server = http.createServer((req,res)=>{
let ins = fs.createReadStream(`www${req.url}`);//得到用户请求的地址
ins.pipe(res);//如果有就写给用户
ins.on('error',err=>{
res.writeHeader(404);
res.write('Not Found');
res.end();
})
});
server.listen(8087);
//例(读写流,gz压缩)
const zlib = require('zlib');
const fs = require('fs');
let ins = fs.createReadStream('1.png');//创建读取流
let outs = fs.createWriteStream('2.png.gz');//创建写入流(后缀gz)
let gz = zlib.createGzip();//创建gz管道
ins.pipe(gz).pipe(outs);//将读取流连接gz在连接写入流
outs.on('finish',()=>{
console.log('成功');
})
//最终例:在网络流中将文件以压缩的形式发送出去,节省流量,省钱.
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
let server = http.createServer((req,res)=>{
let ins = fs.createReadStream(`www${req.url}`);//得到用户请求的地址
//不在是直接将文件给用户,而是经由gz压缩后再给.
res.setHeader('content-encoding','gzip');//需要告诉浏览器,传输给浏览器的是一个压缩版,否则浏览器会当做下载压缩文件
let gz = zlib.createGzip();//创建gz管道
ins.pipe(gz).pipe(res);//将读取的文件压缩后发送给浏览器//节约流量
ins.on('error',err=>{
res.setHeader('content-encoding','');//处理错误时返回的不是NOT found是个文本而不是页面,所以要把头重置,否则就会报错.
res.writeHeader(404);
res.write('Not Found');
res.end();
})
});
server.listen(8087);
扩展
表单的三种POST提交的enctype属性
<form action='xxx' method='post' enctype='text/plain'>
1. text/plain 指该post请求是纯文本.
2. application/x-www/form-urlencoded 默认的,指该post请求为url编码方式.
3. multipart/form-data 指该post是上传一个文件.
网友评论