1. 附件上传

表单的POST存在的三种enctype提交方式。

  1. text/plain: 提交纯文本的数据,用的很少。

  2. application/x-www-form-urlencoded:默认方式, 采用url编码方式

  3. multipart/form-data: 上传文件内容,上传附件时,要选用该种类型。

如果使用x-www-form-urlencoded默认方式,只会上传文件的名字。文件内容不会上传

POST 提交的附件数据, 如下

------WebKitFormBoundarysMq8coSZNCVKkBzi
Content-Disposition: form-data; name="aaa"

12
------WebKitFormBoundarysMq8coSZNCVKkBzi
Content-Disposition: form-data; name="bbb"

13
------WebKitFormBoundarysMq8coSZNCVKkBzi
Content-Disposition: form-data; name="file"; filename="365.png"
Content-Type: image/png


------WebKitFormBoundarysMq8coSZNCVKkBzi--

在请求头中,存在 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarysMq8coSZNCVKkBzi boundary的值作为分隔符

<分隔符>
Content-Disposition: form-data; name="aaa"

12
<分隔符>
Content-Disposition: form-data; name="bbb"

13
<分隔符>
Content-Disposition: form-data; name="file"; filename="365.png"
Content-Type: image/png


<分隔符>--

使用\r\n代替换行,整理数据可得

<分隔符>\r\n
Content-Disposition: form-data; name="aaa"\r\n
\r\n
12\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="bbb"\r\n
\r\n
13\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="file"; filename="365.png"\r\n
Content-Type: image/png\r\n
\r\n
文件内容\r\n
<分隔符>--\r\n

可简化表示为:

<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述1\r\n数据描述2\r\n\r\n文件内容\r\n
<分隔符>--\r\n
  1. 用"<分隔符>"切开数据
[
  空,
  \r\n数据描述\r\n\r\n数据值\r\n,
  \r\n数据描述\r\n\r\n数据值\r\n,
  \r\n数据描述1\r\n数据描述2\r\n\r\n文件内容\r\n,
  --\r\n
]
  1. 丢弃头尾元素
[
  \r\n数据描述\r\n\r\n数据值\r\n,
  \r\n数据描述\r\n\r\n数据值\r\n,
  \r\n数据描述1\r\n数据描述2\r\n\r\n文件内容\r\n,
]
  1. 丢弃每一项的头尾\r\n
[
  数据描述\r\n\r\n数据值,
  数据描述\r\n\r\n数据值,
  数据描述1\r\n数据描述2\r\n\r\n文件内容,
]
  1. 用第一次出现的\r\n\r\n切分
普通数据:[数据描述, 数据值]
或
文件数据:[数据描述1\r\n数据描述2, <文件内容>]
  1. 判断描述的里面有没有\r\n

存在为: 文件数据:[数据描述1\r\n数据描述2, <文件内容>]
不存在: 为普通数据:[数据描述, 数据值]

  1. 分析"数据描述"

用分隔符切割数据
分隔符每次都是随机的,16位字符,字母大小写加数字,共62个字符。
分隔符在请求头中,content-typeboundary, 使用分号空格将boundary切割出来。

2. 对Buffer数据进行操作

buffer并不存在split方法,需要手动扩展:

// 新增Buffer的split方法
Buffer.prototype.split = Buffer.prototype.split || function(b) {
    const arr = [];
    let cur = 0;
    while ((n = this.indexOf(b, cur)) != -1) {
      arr.push(this.slice(cur, n));
      cur = n +b.length;
    }
    arr.push(this.slice(cur));
    return arr;
}

3. 附件上传

const http = require('http');
const fs = require('fs');
const uuid = require('uuid/v4');
Buffer.prototype.split = Buffer.prototype.split || function(b) {
    const arr = [];
    let cur = 0;
    while ((n = this.indexOf(b, cur)) != -1) {
      arr.push(this.slice(cur, n));
      cur = n +b.length;
    }
    arr.push(this.slice(cur));
    return arr;
}

const server = http.createServer((req, res) => {
    if (req.method === 'POST') {
        const arr = []; // 创建一个数组,用于接收前端传入的数据,前端传入的数据为Buffer类型。
        req.on('data', data => { // 持续接收数据
            arr.push(data);
        });
        req.on('end', () => { // 数据传输完成
            const data = Buffer.concat(arr);
            // const data = querystring.parse(str);
            if (req.headers['content-type']) {
                const str = req.headers['content-type'].split('; ')[1];
                if (str) {
                    // 获取到的和真实的相比会少两个-- 手动补上
                    const boundary = `--${str.split('=')[1]}`;
                    let arr = data.split(boundary);
                    // 头和尾数据没用,可以删掉
                    arr.shift();
                    arr.pop();
                    // 丢弃掉每个数据头尾的\r\n
                    arr = arr.map(buffer => buffer.slice(2, buffer.length - 2));
                    const post = {};
                    const files = {};
                    arr.forEach(buffer => {
                        // 每个数据在第一个\r\n处切成两半
                        let n = buffer.indexOf('\r\n\r\n');
                        let disposition = buffer.slice(0, n);
                        let content = buffer.slice(n + 4);
                        // 判断数据是普通数据还是文件数据
                        // 文件数据在content中,所以disposition可以转成字符串,没关系
                        disposition = disposition.toString();
                        // 如果disposition中不存在\r\n, 则为普通数据,否则为附件
                        if (disposition.indexOf('\r\n') == -1) {
                            // 普通数据, content可以保存成字符串
                            content = content.toString();
                            let name = disposition.split('; ')[1].split('=')[1];
                            name = name.substring(1, name.length - 1);
                            post[name] = content;
                        } 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);
                            const path = `upload/${uuid()}`;
                            fs.writeFile(path, content, err => {
                                if (err) {
                                    // 失败
                                } else {
                                    // 成功
                                    files[name] = { filename, path, type };
                                }
                            });
                        }
                    })
                    res.write(JSON.stringify({
                        message: "成功"
                    }));
                    res.end(); // 结束
                }
            }
        })
    } else {
        fs.readFile('./index.html', (err, data) => {
            res.write(data);
            res.end();
        })
    }
});

server.listen(8080);

4. uuid

也叫guid是全球唯一标识符

uuid协议是rfc4122

nodeuuid模块

5. web性能负载

并不是对cpu的负载

6. blob和arrayBuffer的区别

arrayBuffer更贴近于array,本质上是一个array,是用array存储的二进制数据

blob是一个块,本质上是一个数据

转载须知

如转载必须标明文章出处文章名称文章作者,格式如下:

转自:【致前端 - zhiqianduan.com】 文件上传原理解析  "隐冬"
请输入评论...