第8天 二进制文件上传

今天要让post.js插件,解析二进制文件。今天采用的解析方式和昨天完全不同,post.js插件需要重写,当然昨天的基础也是很必要的。

this首先,还是要找到突破口,研究如何让插件支持二进制,并且分析解析原理。然后着手开发一些测试代码,基本的分析工作完成后,再重写post.js插件,最后通过例子进行测试。

yellow今天的内容相对抽象,需要全神贯注和动手实验,一定要动手自己做demo和测试代码。下面就开始今天的开发,希望一切顺利!

二进制解析原理

用二进制格式代替字符串

对二进制进行解析,就不能把上传来的body信息转换为字符串形式处理,而是要用Buffer二进制类型处理。

首先,我们不能用 body_data += chunk 方式合并体数据了,因为合并后是字符串形式,需要采用下面的编码方式代替。

var body_data;
var chunk_list = [];

req.on("data",function(chunk){
    chunk_list.push(chunk);
})

req.on("end",function(){
    body_data = Buffer.concat(chunk_list);
})

通过这种方式,我们得到了二进制Buffer类型对象body_data,Buffer.concat方法可以合并二进制类型数组,返回一个新的Buffer对象。

先不必弄明白所有代码,跟着往下走就好。现在只要明白 body_data 是二进制对象,我们要对它进行解析,找到它各个部分的信息块。

body_data可以理解为是一个byte字节数组,通过body_data[0]可以得到第一个byte。

为什么?

(小胖问:为什么要这样呢?和之前的合并方式有什么差异呢?)

最关键的差异,是当二进制转换为字符形式处理后,再转回二进制形式,里面的数据结构就变化了。通过下面的代码,让你明白。

var buf = new Buffer([0,1,21,0]);
console.log(buf); // <Buffer 00 01 15 00>

var buf2 = new Buffer(buf.toString("ascii"),"ascii");
console.log(buf2);// <Buffer 20 01 15 20>

上面的代码的意思是,把二进制对象buf通过转换为ascii编码的字符串,然后再转回来。这时候数据就不一样了。我们发现里面的 00 转换为 20 ,太深的不必解释,至少知道通过二进制转字符串,再转换回二进制格式,数据就会变化!

所以,结论就出来了,昨天的方式只适合于文本文件的上传。

那该如何是好?

(小帅哥说道:字符串不能用了,直接操作二进制?好像很复杂的样子!)

是的,相对之前的编码要复杂一些。不过,弄明白原理后,就没什么神秘之处了。下面,我们分析一下,到底该如何做,从哪里下手。

二进制的读取和分析,可以从头循环读取,每次抓取一个byte,然后根据上下文,来判断读取的是哪个信息块。比如,读取的是边界字符串部分,还是信息头,或者是主体信息。根据这个思考方向,我们可以编写一些代码:(分析要配合编码,这样将很快把分析落实和验证分析,不要只是想,要想一些就要马上做一些,这是最快的设计与开发的方法)

var position = 0; // 读取的自己位置
for(var len = body_data.length;position<len;){  // 循环读body_data字节
        var b = body_data[position]; // b 就是字节的 int数据。
}

下一步,我们要配合上下文,对 var b进行分析。所谓上下文,我们可以理解为:读取状态,也就是眼下读取的是哪部分数据。所以要定义一下编码:

 // 读取状态 
 // 0表示怀疑是边界字符串
 // 1表示正在读取头部信息字符串
 // 2表示正在读取body体数据
 var readState = 0;

上下文状态,也就是当前读取什么,我们分为三种状态。怀疑读取边界,这个状态的产生,是由于读取到了-这个信息,-二进制转为int是45,每当读取到-就会把readState=0;1表示读取信息头,这里没有怀疑字样,因为如果读取边界之后,就能确定要读取头部信息了;2表示读取body体信息,这个也不需要怀疑,因为读取完头部信息,就可以确定要读取body体数据了。

回过头来再说说,怀疑读取边界信息。为什么要用怀疑字样呢?这是因为除了第一个边界信息外,我们不知道什么时候读完body体信息,如果读到-就说明要开始读取边界信息,这个不准确,因为我们无法确定body信息体内是否包括-

根据以上的分析,我们可以写出如下的编码:

function handle(b){  // 字节处理器
         switch(readState){
            case 0:
            // 处理怀疑是正在读取边界字符串
            break
            case 1:
            // 读取信息头的实际操作
            break;
            case 2:
            // 读取信息体
            break;
         }
}

handle是个处理器,根据readState的状态处理上下文数据。

var backup = [] 备用数据 和 var body = []

bady表示上传的数据体。backup表示一个储存二进制 int 类型数据的数组,也就是当读取数据时,如果不确定是属于哪部分数据,就先存储在备用数据中。当确定之后直接把backup放入body,并清空backup。

以上就是整个的分析过程,其实看书学习,重点在于训练分析能力,而不在于背诵了多少API,API文档就是用来查看的,所以没必要所谓的精通API。当然精通更好。思路清晰了,编码也就水到渠成,分析的过程就是经验之谈,这个和API不同,每个人分析方式都不同,在这里我也只是抛砖引玉。希望通过这个过程,传授你经验,而不是几个API使用的知识。

在全书中,我也一直在强调经验的传授,分析过程大于记忆的理念。我们要提高的是创造力和逻辑分析能力,记忆力现在确实不是什么强项,因为知识从哪里都可以学到,资讯获得太容易,而分析能力和创造力却是很难量化的,也是很难学到的。

下一节,我们要大幅度修改post.js插件的代码,让它具有处理二进制信息的能力。