天天看點

Express 中間件 body-parser 原理分析

Express 中間件 body-parser 原理分析

閱讀原文

前言

Express

是基于 NodeJS 平台的 Web 架構,應用廣泛,在

Express

社群中有着大量的開發者通過

Express

中間件的特性,開發了各種功能的中間件,用來處理某些響應以及給請求對象

req

、響應對象

res

添加屬性或方法,我們接下來就通過分析常用的

body-parser

中間件的原理來了解如何開發

Express

中間件,如果想了解更多

Express

内部封裝原理可以看 《Express 源碼分析及簡易封裝》。

body-parser 的基本使用

想刨析一個中間件的原理,首先應該從使用入手,在足夠了解用法的基礎上去分析,現在搭建一個簡易的

Express

服務,并使用

body-parser

中間件,使用前需安裝。

npm install express body-parser

使用 body-parser 代碼如下:

const express = require("express");
const bodyParser = require("body-parser");

// 建立服務
const app = express();

// 使用 body-parser 中間
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// 建立路由
app.post("/login", function (req, res) {
    console.log(req.body);
    res.send(req.body);
});

// 監聽服務
app.listen(3000, function () {
    console.log("server start 3000");
});
           

啟動上面的伺服器,通過

postman

工具分别通過表單送出和

json

的格式通路 http://localhost:3000/login,檢視伺服器控制背景的列印結果和

postman

的傳回結果。

body-parser 的實作

1、原理分析

從上面的使用案例我們可以分析出一下幾點:

  • 首先,

    body-parser

    中間件的作用是給

    req

    添加屬性

    body

    ,值為對象,以鍵值對的形式存儲請求體中的參數;
  • 其次,

    body-parser

    隻處理

    POST

    請求;
  • 最後,

    body-parser

    子產品導出一個對象,上面有兩個方法

    urlencoded

    json

    ,分别處理表單送出和

    json

    格式的請求體參數。

2、分析 urlencoded、json 公共邏輯

在實作之前我們先分析一下兩個方法,首先都需要先讀取請求體中的内容,資料傳輸的類型為 Buffer,轉換成字元串後會根據送出方式不同而導緻請求體中的内容是查詢字元串或者是

json

字元串的差別。

當解析失敗時都需要做錯誤處理,當不是

POST

請求時都需要向下執行其他中間件,而最核心的事就是把請求體中的資料轉換成對象挂在

req.body

上。

使用的轉換資料的方法不同是唯一的差別,能區分兩者的就是請求頭

Content-Type

的值,是以我們可以把所有的公共邏輯抽取出來用一個

acceptPost

函數來執行。

3、子產品的建立

我們下面建立自己的

body-parser

子產品,防止命名沖突,我們的子產品命名為

my-body-parser

,處理參數需要使用

querystring

qs

兩個子產品,其中

qs

是第三方子產品,使用前需安裝。

npm install qs

qs

querystring

作用基本相同,就是處理查詢字元串格式的參數,但是也有一點小小的差別,

querystring

隻能處理一級,而

qs

可以處理多級。

const querystring = require("querystring");
const qs = require("qs");

// urlencoded 和 json 公共邏輯
function acceptPost() {
    // ...
}

// 處理表單送出的方法
function urlencoded() {
    // ...
}

// 處理請求體 json 的方法
function json() {
    // ...
}

// 導出對象
module.exports = { urlencoded, json };
           

在把基本子產品搭建好後,我們下面就實作

body-parser

子產品内的公共邏輯函數

acceptPost

4、acceptPost 的實作

為了相容

urlencoded

方法和

json

方法設計了兩個參數,一個是區分目前調用方法的

type

,一個是針對

urlencoded

方法的

options

// acceptPost 的實作
// urlencoded 方法和 json 方法的公共邏輯函數
function acceptPost(type, options) {
    // 傳回一個中間件函數
    return function (req, res, next) {
        // 擷取請求頭
        let contentType = req.headers["content-type"];

        // 判斷如果不符合兩種送出的請求頭直接交給其他中間件處理
        if (
            contentType === "application/x-www-form-urlencoded" ||
            contentType === "application/json"
        ) {
            // 存儲資料的數組
            let buffers = [];

            req.on("data", function (data) {
                // 接收資料并存入數組中
                buffers.push(data);
            });

            req.on("end", function () {
                // 組合資料并轉換成字元串
                let result = Buffer.concat(buffers).toString();

                // 處理資料并挂載 req.body 屬性上
                // 如果是表單送出則使用 querystring 或 qs,否則使用 JSON.parse
                if (type === "form") {
                    // 如果配置 extended 值為 true 使用 qs,否則使用 querystring
                    req.body = options.extended ? qs.parse(result) : querystring.parse(result);
                } else if(type === "json") {
                    req.body = JSON.parse(result);
                }

                next(); // 向下執行
            });

            // 錯誤處理
            req.on("err", function (err) {
                next(err);
            });
        } else {
            next();
        }
    }
}
           

5、urlencoded 和 json 方法的實作

// 處理表單送出的方法
function urlencoded(options) {
    // 定義 type 值
    let type = "form";
    return acceptPost(type, options)
}

// 處理請求體 json 的方法
function json() {
    // 定義 type 值
    let type = "json";
    return acceptPost(type);
}
           

當我們把所有的公共邏輯都抽取出去後發現,

urlencoded

json

方法内部隻需要定義不同的類型就可以執行自己的中間件邏輯。

總結

上面分析

body-parse

中間件的原理的目的在于了解

Express

中間件開發的模式,在此總結一下,

Express

中間件傳回的是一個函數,形參為

req

res

next

,當功能無法處理某些情況時需要調用

next

,當出現錯誤時調用

next

并傳遞錯誤,則交給

Express

内置的錯誤進行中間件,在中間件内部代碼涉及異步操作時,須在異步完成的回調當中調用

next

,這是不如

Koa

友善的一點,同時也是兩者的差別,因為

Koa

中已經大量使用

async/await

,在執行異步代碼時可以等待。

繼續閱讀