升级post.js插件
以下是post.js插件升级后的代码
module.exports = function post(req,res,next){
var body_data
,chunk_list = []
,contentType = req.headers["content-type"]
,contentLength = parseInt(req.headers["content-length"])
// isMulti 如果是true,表示是设置了enctype="multipart/form-data" 属性
,isMulti = /(boundary=)/gi.test(contentType)
,boundary = RegExp["$'"]
,boundaryStandard = "--"+boundary+"\r\n"
,boundaryEnd = "--"+boundary+"--\r\n";
req.body = {};
req.files = {};
req.on("data",function(chunk){
chunk_list.push(chunk);
})
req.on("end",function(){
body_data = Buffer.concat(chunk_list);
if(isMulti){
// 备用数据
var backup = [];
// 读取状态
// 0表示怀疑是边界字符串
// 1表示正在读取头部信息字符串
// 2表示正在读取body体数据
var readState = 0;
// body体数据
var body = []
// 正在读取的body_data的位置
var position = 0;
function handle(b){
switch(readState){
case 0:
// 确定是否是边界字符串
if(body_data.slice(position,position+boundaryStandard.length).toString() === boundaryStandard){
if(backup.length > 0){
body.push(backup);
backup = [];
}
else{
position += boundaryStandard.length;
readState = 1;
}
}else if(body_data.slice(position,position+boundaryEnd.length).toString() === boundaryEnd){
if(backup.length > 0){
body.push(backup);
}
return true;
}else{
backup.push(b);
position += 1;
}
break;
case 1:
if(backup.length>=3){
// backup得到后三位
var arr3 = backup.slice(backup.length-3,backup.length);
arr3.push(b);
backup.push(b);
// 验证是否是 \r\n\r\n
if(new Buffer(arr3).toString() === "\r\n\r\n"){
body.push(backup);
backup = [];
readState = 2;
}
}else{
backup.push(b);
}
position +=1;
break;
case 2:
backup.push(b);
position += 1;
break;
}
}
for(var len = body_data.length;position<len;){
var b = body_data[position];
// 判断是否为 "-"
if(readState === 0 || readState === 2){
if(b === 45){ // 判断是否是 "-"
readState = 0;
}else{
readState = 2;
}
}
var end = handle(b);
if(end){ // 解析结束,从body中解析出fieldName和对应的数据。
for(var i=0,len=body.length;i<len;){
var header = new Buffer(body[i]).toString();
// 数组转换二进制,为删除后面的 \r\n
var arr = body[i+1];
var data = new Buffer(arr.slice(0,arr.length-2));
// 从头信息中解析出表单字段的名称,也就是表单的name属性值。
/name=\"(.*?)\"/g.test(header);
var fieldName = RegExp.$1;
// 判断是上传的文件,还是一般的表单字段。
var isFile = /filename/g.test(header);
if(isFile){
req.files[fieldName] = data;
}else{
req.body[fieldName] = data.toString();
}
i+=2; // 每次跳过两个,因为信息头和信息体为一对。
}
break;
}
}
}else{
try{
var qs = require("querystring");
req.body = qs.parse(body_data);
}catch(e){}
}
next();
})
}看起来代码很多,几乎更改了昨天全部的代码,结合上一节的分析,可以看懂post.js插件代码的大概。下面,我们具体分析一下代码细节。
new Buffer( int 数组 )
通过 new Buffer(int数组)可以创建二进制数据对象。值得注意的是,int数组中的数字最大不能大于255,比如 [255,259,90] 这样写不会报错,但不会得到预期效果,这个限制是由于一个byte有八个bit组成限制的,简单知道这一点即可。
Buffer.concat(Buffer类型数组)
这是Buffer类的静态方法,可以连接多个Buffer对象,并返回一个新对象。在post.js代码中,我们利用它合并客户端发送来的信息。
position
代码中position 并不一定随着for循环而每次递增1,它是随着程序中逻辑判断而更改的。比如,确定读取的是信息头,会直接跳到信息体头部开始读,怎么跳呢?参看这段代码: position += boundaryStandard.length 。
buffer.slice(start,end)
这个方法可以截取Buffer对象的一部分,并返回一个截取部分的Buffer对象。
这个插件的实现方式,不是最好的,因为对内存消耗比较大,不过这种方式相对直观,便于讲解。今天结束开发之前,会介绍一些其他的实现方式,可以自己尝试一下。
下一节,我们会写一段代码进行演示。