升级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对象。


这个插件的实现方式,不是最好的,因为对内存消耗比较大,不过这种方式相对直观,便于讲解。今天结束开发之前,会介绍一些其他的实现方式,可以自己尝试一下。

下一节,我们会写一段代码进行演示。