Express 中間件 body-parser 原理分析

在這裏插入圖片描述


閱讀原文


前言

Express 是基於 NodeJS 平臺的 Web 框架,應用普遍,在 Express 社區中有着大量的開發者經過 Express 中間件的特性,開發了各類功能的中間件,用來處理某些響應以及給請求對象 req、響應對象 res 添加屬性或方法,咱們接下來就經過分析經常使用的 body-parser 中間件的原理來了解如何開發 Express 中間件,若是想了解更多 Express 內部封裝原理能夠看 《Express 源碼分析及簡易封裝》express


body-parser 的基本使用

想刨析一箇中間件的原理,首先應該從使用入手,在足夠了解用法的基礎上去分析,如今搭建一個簡易的 Express 服務,並使用 body-parser 中間件,使用前需安裝。npm

npm install express body-parser

使用 body-parser 代碼以下:json

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 的實現

一、原理分析

從上面的使用案例咱們能夠分析出一下幾點:服務器

  • 首先,body-parser 中間件的做用是給 req 添加屬性 body,值爲對象,以鍵值對的形式存儲請求體中的參數;
  • 其次,body-parser 只處理 POST 請求;
  • 最後,body-parser 模塊導出一個對象,上面有兩個方法 urlencodedjson,分別處理表單提交和 json 格式的請求體參數。

二、分析 urlencoded、json 公共邏輯

在實現以前咱們先分析一下兩個方法,首先都須要先讀取請求體中的內容,數據傳輸的類型爲 Buffer,轉換成字符串後會根據提交方式不一樣而致使請求體中的內容是查詢字符串或者是 json 字符串的區別。app

當解析失敗時都須要作錯誤處理,當不是 POST 請求時都須要向下執行其餘中間件,而最核心的事就是把請求體中的數據轉換成對象掛在 req.body 上。框架

使用的轉換數據的方法不一樣是惟一的區別,能區分二者的就是請求頭 Content-Type 的值,所以咱們能夠把全部的公共邏輯抽取出來用一個 acceptPost 函數來執行。異步

三、模塊的建立

咱們下面建立本身的 body-parser 模塊,防止命名衝突,咱們的模塊命名爲 my-body-parser,處理參數須要使用 querystringqs 兩個模塊,其中 qs 是第三方模塊,使用前需安裝。async

npm install qs

qsquerystring 做用基本相同,就是處理查詢字符串格式的參數,可是也有一點小小的區別,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

四、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();
        }
    }
}

五、urlencoded 和 json 方法的實現

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

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

當咱們把全部的公共邏輯都抽取出去後發現,urlencodedjson 方法內部只須要定義不一樣的類型就能夠執行本身的中間件邏輯。


總結

上面分析 body-parse 中間件的原理的目的在於理解 Express 中間件開發的模式,在此總結一下,Express 中間件返回的是一個函數,形參爲 reqresnext,當功能沒法處理某些狀況時須要調用 next,當出現錯誤時調用 next 並傳遞錯誤,則交給 Express 內置的錯誤處理中間件,在中間件內部代碼涉及異步操做時,須在異步完成的回調當中調用 next,這是不如 Koa 方便的一點,同時也是二者的區別,由於 Koa 中已經大量使用 async/await,在執行異步代碼時能夠等待。

相關文章
相關標籤/搜索