POST请求,文件上传/解析数据/UUID/流操作、gz压缩

2019-07-09  本文已影响0人  子心_

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是上传一个文件. 
上一篇 下一篇

猜你喜欢

热点阅读