跨域

因为浏览器同源策略的原因,会对 域名,端口,协议进行校验,如果不能保证三者相同,则存在跨域拦截,即请求无法获取到服务器的返回数据

三种方式可以解决跨域

服务端设置cors案例

const http = require('http');

const server = http.createServer((req, res) => {
    // 允许跨域的域名http://127.0.0.1:5500, 如果设置为 * 号,代表允许任何域名访问
    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500'); 
    res.write("123");
    res.end();
});
server.listen(8080);

ejs模板的使用

const ejs = require('ejs');
const fs = require('fs');
const http = require('http');

const server = http.createServer((req, res) => {
    const data = { name: 'yd', age: 18 };
    const template = fs.readFileSync(path.resolve(__dirname, 'index.ejs'), 'utf-8');
    const str = ejs.render(template, data);
    res.write(str);
    res.end();
});

server.listen(8080);
<div>
<%=name%>  
<%=age%>
</div>

ejs 解析原理

function render(template, data) {
    return template.replace(/<%=([\s\S]*?)%>/g, function () {
        return data[arguments[1]];
    })
}

复杂一点的代码
<div>
<%arr.forEach(a => {%>
    <li><%=a%></li>
<%})%>
</div>
function render(template, data) {
    template = template.replace(/<%=([\s\S]*?)%>/g, function() {
        return '${' + arguments[1] + '}';
    })
    let head = 'let str;\r\nwith(data){\r\n';
    head += 'str=`';
    const content = template.replace(/<%([\s\S]*?)%>/g, function() {
        return '`\r\n' + arguments[1] + '\r\nstr+=`';
    });
    const tail = '`\r\n}\r\nreturn str;';
    const fn = new Function('data', head + content + tail);
    return fn(data);
}

附件上传

表单的三种POST提交方式 enctype

  1. text/plain: 提交纯文本的数据,用的很少。
  2. application/x-www-form-urlencoded:默认方式, 采用url编码方式
  3. multipart/form-data: 上传文件内容,上传附件时,要选用该种类型。

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

------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
    ]

  2. 丢弃头尾元素
    [
    \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,
    ]

  3. 丢弃每一项的头尾\r\n
    [
    数据描述\r\n\r\n数据值,
    数据描述\r\n\r\n数据值,
    数据描述1\r\n数据描述2\r\n\r\n文件内容,
    ]

  4. 用第一次出现的"\r\n\r\n"切分
    普通数据:[数据描述, 数据值]

    文件数据:[数据描述1\r\n数据描述2, <文件内容>]

  5. 判断描述的里面有没有"\r\n"
    存在为: 文件数据:[数据描述1\r\n数据描述2, <文件内容>]
    不存在: 为普通数据:[数据描述, 数据值]

  6. 分析"数据描述"

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

对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;
}

附件上传

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);

uuid

也叫guid,全球唯一标识符

一切协议都是rfc,uuid协议是rfc4122

node中uuid模块

web性能负载

并不是对cpu的负载

blob 和arrayBuffer的区别