Node.js Request 解析:掌握 Buffer、Streams 與數據處理

在 Express 的開發環境中,我們習慣使用 express.json()multer 來處理客戶端傳來的數據。但如果我們想深入了解 Node.js 的運作原理,或是想建立一個極致輕量、零依賴的服務,就必須學會如何原生地處理 IncomingMessage 數據流。

這篇文章將帶你從底層的角度,解析不同類型的 Request 數據。

為什麼要注意 Request 解析?

Node.js 中的 req 物件是一個 Readable Stream。數據並非一次性到達,而是分塊 (Chunks) 傳輸的。如果處理不當,可能會造成記憶體溢出 (OOM) 或安全漏洞(例如攻擊者傳送無限大的數據)。

1. 解析 JSON 數據 (原生 Buffer 拼接)

這是處理 POST 請求最基礎的方式:監聽 data 事件並將二進位 Buffer 拼接起來。

const http = require('http');

http
  .createServer((req, res) => {
    if (req.method === 'POST' && req.headers['content-type'] === 'application/json') {
      let body = [];

      req.on('data', (chunk) => {
        // 建議: 檢查數據大小,防止惡意攻擊
        if (body.length > 1e6) {
          // 大於 1MB 則斷開連線
          req.destroy();
        }
        body.push(chunk);
      });

      req.on('end', () => {
        // 將所有的 Chunk 拼接成單個 Buffer 並轉為字串
        const data = Buffer.concat(body).toString();
        try {
          const json = JSON.parse(data);
          console.log('收到 JSON:', json);
          res.end('JSON 已收到');
        } catch (e) {
          res.statusCode = 400;
          res.end('格式錯誤');
        }
      });
    }
  })
  .listen(3000);

2. 解析原生表單 (URL-Encoded)

傳統的 HTML 表單數據會以 key1=value1&key2=value2 的方式傳輸。

const { decode } = require('querystring');

// 在 req.on('end') 中處理內容
req.on('end', () => {
  const rawBody = Buffer.concat(body).toString();
  const formData = decode(rawBody);
  console.log('用戶姓名:', formData.username);
});

3. 現代化:原生處理 Multipart (FormData)

Node.js 18+ 開始,Node.js 內建了符合 Web 標準的 FormDataFile API。雖然 IncomingMessage 本身不直接提供這些方法,但我們可以利用現代的 ReadableStream 進行轉化。

示範:手動解析 Multipart (不使用外部套件)

對於簡單的檔案上傳,我們可以讀取 boundary 並自行切割 Buffer。但在實務中,更推薦利用 Web 標準介面:

const { Readable } = require('node:stream');

// 將原生的 Node req (Readable) 轉換為標準的 Web ReadableStream
const webStream = Readable.toWeb(req);

// 封裝成 Request 物件以利用內建的 formData()
// 注意:這需要 headers 資訊
const standardReq = new Request('http://localhost', {
  method: req.method,
  headers: req.headers,
  body: webStream,
  duplex: 'half',
});

// 使用標準 API 解析
const formData = await standardReq.formData();
const file = formData.get('myFile');
console.log('上傳檔案名稱:', file.name);

實務開發的安全建議

  1. 嚴格限制 Content-Length:永遠不要信任客戶端宣告的大小,務必在讀取 data 時手動累計 bytes 數量。
  2. 正確處理編碼:拼接 Buffer 時,優先使用 Buffer.concat(chunks) 而非 str += chunk,後者在處理多位元組字元(如中文)時可能導致亂碼。
  3. 優雅超時:如果客戶端保持連線但不傳送數據,應主動關閉連線。

總結

  1. req 是串流:數據是分段進來的,必須妥善拼接與限制大小。
  2. Buffer.concat 是處理二進位數據的最安全方式。
  3. 隨著 Node.js 對 Web Standard 的支援提高,我們可以直接使用 formData() 等 API 進行優雅的解析。