天天看点

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

,在执行异步代码时可以等待。

继续阅读