用原生node写一个文件上传的中间件

直奔主题,就不墨迹了,文件数据是post数据,html表单里面post支持三种enctype,

文件上传的那种是multipart/form-data。做来做个试验。

const http = require('http');
const fs = require('fs');
let server = http.createServer((req,res) => {
  let str = '';
  req.on('data',(data) => {
    str+=data;
  });
  req.on('end',() => {
    console.log(str);
    res.end();
  })
})

server.listen(8080, () => {
  console.log(`服务器已启动,监听在8080端口`);
});
复制代码

html

<form action="http://localhost:8080" enctype="multipart/form-data" method="post">
    <input type="text" name="user" id="">
    <input type="password" name="pass" id="">
    <input type="file" name="f1" id="">
    <input type="submit" value="提交">
  </form>
复制代码

得到的结果是这样的

这里的html文件里的内容没有都截出来。所以看起来有点奇怪,我们来分析一下这段数据,显然------WebKitFormBoundaryDqs4grRjEIPy5ZmY这个东东是分隔符。把这个东西称为分隔符没毛病吧,然后数据就变成了

分隔符
Content-Disposition: form-data; name="user"

chen
分隔符
Content-Disposition: form-data; name="pass"

123321`1
分隔符
Content-Disposition: form-data; name="f1"; filename="test.html"
Content-Type: text/html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
复制代码

然后把换行换成\r\n,数据就变成了这样

<分隔符>\r\n
Content-Disposition: form-data; name="user"\r\n
\r\n
chen\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="pass"\r\n
\r\n
123321`1\r\n
<分隔符>\r\n
Content-Disposition: form-data; name="f1"; filename="test.html"\r\n
Content-Type: text/html\r\n
\r\n
<文件内容>\r\n
<分隔符>--
复制代码

然后把Content-Disposition: form-data;用数据类型描述换一下,为了方便分析数据,把它对应的值换成数据值。

<分隔符>\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数据描述\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
]

这里的空,可能有些疑惑,不过你反应5秒钟应该就不疑惑了。空和最后一个数据没有价值,
于是我们把他们去掉,于是数据就变成了这样
[
  \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,把这个也去掉,数据就变成了这样

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

复制代码

然后再用\r\n\r\n对数据在切一次,就变成了这样

普通数据:[数据描述, 数据值]
  或
  文件数据:[数据描述1\r\n数据描述2, <文件内容>]
复制代码

到这就可以动手写代码了,

const http=require('http');
const fs=require('fs');
const uuid=require('uuid/v4');
Buffer.prototype.split=Buffer.prototype.split||function (b){
  let arr=[];

  let cur=0;
  let n=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;
};
let server=http.createServer((req, res)=>{
  let arr=[];

  req.on('data', data=>{
    arr.push(data);
  });
  req.on('end', ()=>{
    let data=Buffer.concat(arr);
    //data
    //解析二进制文件上传数据
    let post={};
    let files={};
    if(req.headers['content-type']){
      let str=req.headers['content-type'].split('; ')[1];
      if(str){
        let boundary='--'+str.split('=')[1];

        //1.用"分隔符切分整个数据"
        let arr=data.split(boundary);

        //2.丢弃头尾两个数据
        arr.shift();
        arr.pop();

        //3.丢弃掉每个数据头尾的"\r\n"
        arr=arr.map(buffer=>buffer.slice(2,buffer.length-2));

        //4.每个数据在第一个"\r\n\r\n"处切成两半
        arr.forEach(buffer=>{
          let n=buffer.indexOf('\r\n\r\n');

          let disposition=buffer.slice(0, n);
          let content=buffer.slice(n+4);

          disposition=disposition.toString();

          if(disposition.indexOf('\r\n')==-1){
            //普通数据
            //Content-Disposition: form-data; name="user"
            content=content.toString();

            let name=disposition.split('; ')[1].split('=')[1];
            name=name.substring(1, name.length-1);

            post[name]=content;
          }else{
            //文件数据
            /*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
            Content-Type: text/plain*/
            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);

            let path=`upload/${uuid().replace(/\-/g, '')}`;

            fs.writeFile(path, content, err=>{
              if(err){
                console.log('文件写入失败', err);
              }else{
                files[name]={filename, path, type};
                console.log(files);
              }
            });
          }
        });

        //5.完成
        console.log(post);
      }
    }
    res.end();
  });
});
server.listen(8080,() => {
  console.log('服务器启动在8080端口);
})


复制代码

到这就可以测试了。

灵感来源blue课堂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值