Node中POST請求的正確處理方式

前端精髓發表於2019-06-19

Node的 http 模組只對HTTP報文的頭部進行了解析,然後觸發 request 事件。如果請求中還帶有內容部分(如 POST 請求,它具有報頭和內容),內容部分需要使用者自行接收和解析。

通過報頭的 Transfer-EncodingContent-Length 即可判斷請求中是否帶有內容

欄位名稱 含義
Transfer-Encoding 指定報文主體的傳輸編碼方式
Content-Length 報文主體的大小

寫個方法判斷是否有報文主體

const hasBody = function(req) {
  return  'transfer-encoding'  in  req.headers  ||  'content-length' in req.headers;
};
複製程式碼

接收資料

報文內容部分會通過 data 事件觸發,我們只需以流的方式處理即可,不要在訂閱 data 事件的時候使用 += 的形式拼裝資料,這樣會亂碼的。

function handle(req, res) {
  if (hasBody(req)) {
    var buffers = [];
    req.on('data', function (chunk) {
      buffers.push(chunk);
    });
    req.on('end', function () {
      const POST = Buffer.concat(buffers).toString();
    });
  }
}
複製程式碼

1. POST傳送的是表單的資料

如果在頁面中使用表單提交一個post請求,我們的程式碼大概是這樣的。

  <form action="/upload" method="post">
    <label for="username">使用者名稱:</label>
    <input type="text" name="username" id="username" />
    <label for="password">密碼:</label>
    <input type="password" name="password" id="password" />
    <input type="submit" />
  </form>
複製程式碼

預設的表單提交,請求頭中的 Content-Type 欄位值為 application/x-www-form-urlencoded

Content-Type: application/x-www-form-urlencoded
複製程式碼

寫一個判斷內容型別的方法

const mime = function (req) {
  const str = req.headers['content-type'] || '';
  return str.split(';')[0];
};
複製程式碼

它的報文體內容跟查詢字串相同

username=Tom&password=123456
複製程式碼

解析表單資料使用querystring模組中的parse方法

const querystring = require('querystring')
function handleForm (req, res) {
  const isFrom = mime(req) === 'application/x-www-form-urlencoded'
  if (hasBody(req) && isFrom) {
    var buffers = [];
    req.on('data', function (chunk) {
      buffers.push(chunk);
    });
    req.on('end', function () {
      let requestBody = Buffer.concat(buffers).toString();
      requestBody = querystring.parse(requestBody)
    });
  }
}
複製程式碼

2. POST傳送的是JSON的資料

如果在頁面中使用axios傳送post請求,我們的程式碼大概是這樣的。

axios.post('/user', {
  username: 'Tom',
  password: '123456'
})
複製程式碼

預設的JSON提交,請求頭中的 Content-Type 欄位值為 application/json,在 Content-Type 中可能還附帶編碼資訊 charset=utf-8

Content-Type: application/json; charset=utf-8
複製程式碼

它的報文體內容跟JSON格式的字串相同

{
  "name": "Tom",
  "password": "123456"
}
複製程式碼

解析JSON資料使用 JSON.parse 方法。

function handleJson (req, res) {
  const isJson = mime(req) === 'application/json'
  if (hasBody(req) && isJson) {
    var buffers = [];
    req.on('data', function (chunk) {
      buffers.push(chunk);
    });
    req.on('end', function () {
      let requestBody = Buffer.concat(buffers).toString();
      try {
        requestBody = JSON.parse(requestBody)
      } catch (error) {
        console.log(error)
      }
    });
  }
}
複製程式碼

3. POST傳送的是檔案資料

如果在頁面中使用表單提交檔案請求,我們的程式碼大概是這樣的。

  <form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="avatar" id="avatar">
    <input type="submit" />
  </form>
複製程式碼

預設的上傳檔案提交,請求頭中的 Content-Type 欄位值為multipart/form-data,在 Content-Type 中可能還附帶內容分隔符 boundary=----WebKitFormBoundary4Hsing01Izo2AHqv

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4Hsing01Izo2AHqv
複製程式碼

先上傳一個JS檔案,看看報文主體裡面的格式大概是這樣的,包含檔案資訊和檔案內容,有指定的分隔符包裹。

在這裡插入圖片描述

上傳檔案的時候是要區分文字檔案和二進位制檔案,文字檔案是要使用 utf8 編碼(HTML,CSS,JavaScript),二進位制檔案是要使用 binary 編碼(圖片,視訊,音訊)

根據內容分隔符解析上傳的圖片,並且寫入到檔案中,下面程式碼暫時只處理圖片格式的檔案。

function handleFile(req, res) {
  const isFile = mime(req) === 'multipart/form-data'
  if (hasBody(req) && isFile) {
    var buffers = [];
    req.on('data', function (chunk) {
      buffers.push(chunk);
    });
    req.on('end', function () {
      // 處理檔名
      let requestBody = Buffer.concat(buffers).toString('binary');
      let file = querystring.parse(requestBody, '\r\n', ': ')
      let fileInfo = file['Content-Disposition']
      fileInfo = Buffer.from(fileInfo, 'binary').toString()
      let { filename } = querystring.parse(fileInfo, '; ', '=')
      filename = filename.slice(1, -1)
      filename = `./static/${filename}`
      // 處理內容
      let boundary = req.headers['content-type'].split('; ')[1].replace('boundary=', '');
      let contentType = file['Content-Type']
      if (!contentType.includes('image')) return
      let upperBoundary = requestBody.indexOf(contentType) + contentType.length;
      let shorterData = requestBody.substring(upperBoundary)
      let binaryDataAlmost = shorterData.trim()
      let binaryData = binaryDataAlmost.substring(0, binaryDataAlmost.indexOf(`--${boundary}--`))
      // 寫入檔案
      fs.writeFile(filename, binaryData, 'binary', (err) => {
        if (err) {
          console.log('上傳失敗')
        } else {
          console.log('上傳成功', filename)
        }
      })
    });
  }
}
複製程式碼

這就是所有處理POST請求的方式,你都學會了嗎?

相關文章