相信很多人在 import express 的時候都知道要使用 bodyParser,否則會拿不到 request 的 body:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
一直都知道要這樣用,但卻不太知道為什麼,再加上之前用 Node.js 寫 http server 的時候,在沒有用 bodyParser 的情況下去 console.log(req.body) 是 undefined,讓我又覺得更奇怪了,所以決定深入探討一下箇中原因!
Table of Contents
1. init 一個迷你專案
1.1 安裝 express
$ yarn init
$ yarn add express
1.2 Build http server
安裝完 express 後來簡單架一個 http server:
// index.js
const express = require('express');
const app = express();
app.use((req, res) => {
console.log(req.body);
res.sendStatus(200);
});
app.listen(3000, () => {
console.log('server is running in port 3000');
})
這是沒有 body parser 的情況,並且把 request 的 body console.log 出來,接著先把這個 server run 起來:
$ node index.js
有看到 server is running in port 3000 就代表成功了:
1.3 Send http post request
再來要對這個 http server send request,來看看沒有 bodyParser 的情況下拿到的 request.body 是什麼:
這邊我選擇用 Postman 做測試,你也可以選擇用 curl,記得用 Postman 的話要把 http method 選為 POST, body 的格式選擇 JSON。接著按下 Send 就會對我們的 http server send request,可以觀察 terminal 會 console 出什麼:
印出的是 undefined。那加上 body parser 之後呢?
const express = require('express');
const app = express();
app.use(express.json());
app.use((req, res) => {
console.log(req.body);
res.sendStatus(200);
});
app.listen(3000, () => {
console.log('server is running in port 3000');
})
這邊直接用 express.json() 取代 body parser,詳情可以參考:解決 body-parser 被標記為棄用(body-parser as deprecated),兩個寫法的效果一樣。改完 code 後重新跑 server,再 send 一次 request:
結果竟然就拿得到 request 中的 body 了!
2. 深入探討 http request 中的 body
首先我們要先了解到網路傳輸最小的單位:封包,當我們用網路傳送資料的時候,會將資料拆分為封包,最後到目的地在組成原來的資料,http request 的 body 也是一樣的原理,根據 Node.js 官方 document 我們可以用 req.on 來監聽用 request 傳送進來的資料(記得將 body parser 拿掉):
const express = require('express');
const app = express();
app.use((req, res) => {
req.on('data', chunk => {
console.log(chunk)
})
req.on('end', () => {
//end of data
})
res.sendStatus(200);
});
app.listen(3000, () => {
console.log('server is running in port 3000');
})
2.1 body 的真實樣貌
接著再 send 一次 http request,觀察 termianl:
結果竟然跑出一串沒看過的型別?!莫急莫慌莫害怕,Buffer 是 Node.js 用來處理二進位的物件,而 http request,其實會以 readable stream 的方式傳到 server,那什麼又是 stream 呢?可以把 stream 理解為資料流,資料不是一次一整包傳到目的地,而是拆分成數個 chunk 傳到目的地,會先放到 Buffer 中,也就成了我們 console 出的樣子。
想多了解 stream 是什麼的話,可以參考這篇:Understanding Streams in Node.js,個人覺得寫得滿完整的。
2.2 還原 body parser
那如果不用 body parser 或是 express 的 express.json() 該如何將這堆 chunk 轉換為我們的資料呢?Node.js 提供了幾種寫法:
方法一:
app.use((req, res) => {
let data = '';
req.on('data', chunk => {
data += chunk;
})
req.on('end', () => {
console.log(JSON.parse(data));
})
res.sendStatus(200);
});
方法二:
app.use((req, res) => {
let data = [];
req.on('data', chunk => {
data.push(chunk);
})
req.on('end', () => {
data = Buffer.concat(data).toString();
console.log(data);
})
res.sendStatus(200);
});
方法三:
app.use(async (req, res) => {
const buffers = [];
for await (const chunk of req) {
buffers.push(chunk);
}
const data = Buffer.concat(buffers).toString();
console.log(data);
res.sendStatus(200);
});
三種方法都可以把原本的 req.body 給 console 出來,而不管是 express.json() 或是 body parser 也都是在做這件事而已。
個人認為 stream 和 buffer 都算比較抽象的東西,很難解釋清楚,再加上個人目前也還沒有實作過,可能無法解釋的太清楚,希望哪天真的有機會接觸到再來好好整理成白話文~
3. 參考資料
解決 body-parser 被標記為棄用(body-parser as deprecated)
How bodyParser() works
Understanding Streams in Node.js
[試著把切版專案升級到 gulp4.0 吧] Day10 第一個插件:以複製檔案為例,談 node.js 的 stream 與 pipe()
What is chunk in Node.js ?
【我可以你也可以的Node.js】第十四篇 – Node’s file system – 使用串流的方式讀寫檔案
Stream | Node.js v16.7.0 Documentation
如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!