1. 程式人生 > >Node解析POST資料

Node解析POST資料

原理

表單的POST提交主要有三種資料格式:

  1. text/plain
  2. application/x-www-form-urlencoded
  3. multipart/form-data

其中,text/plain用的很少;application/x-www-form-urlencoded是預設,採用url編碼方式,以xxx=xxx&xxx=xx...格式傳輸資料;multipart/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 <分隔符>–

實現思路

因此,服務端可以通過以下步驟來解析資料: 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, <檔案內容>] 沒有——普通資料:[資料描述, 資料值]

例項

獲取分隔符

分隔符是一組隨機字串,如何獲取分隔符?可以通過req.headers['content-type']獲取 在這裡插入圖片描述

API

1.查詢 indexOf() 2.擷取 slice(s, e) 3.切分 split 由於Buffer不提供split方法,因此需要我們自己實現:

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

完整程式碼

const http=require('http');
const common=require('./libs/common');    //split 方法
const fs=require('fs');
const uuid=require('uuid/v4');

let server=http.createServer((req, res)=>{
  let arr = [];
  req.on('data', data => {
    arr.push(data);
  });

  req.on('end', () => {
    let data = Buffer.concat(arr);

    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((elem) => elem.slice(2, elem.length-2));

        // 4、用第一次出現的\r\n\r\n將資料進行切分
        arr.forEach(elem => {
          let n = elem.indexOf('\r\n\r\n');

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

          disposition = disposition.toString();

          //5、判斷描述的裡面有沒有"\r\n"
          if(disposition.indexOf('\r\n') == -1) {
            // console.log('普通資料');

            //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 {
            // console.log('檔案資料');

            /*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
            Content-Type: text/plain*/
            let [,name,filename] = disposition.split('\r\n')[0].split(';');
            let type = disposition.split('\r\n')[1].split(':')[1];

            name = name.split('=')[1].substring(1,name.length-1);
            filename = filename.split('=')[1].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);
              }
            });
          }

        })
        console.log(post);
      }
    }
    res.end();
  })
});
server.listen(1337);

輸出: 在這裡插入圖片描述