Express
是基于 NodeJS 平台的 Web 框架,应用普遍,在 Express
社区中有着大量的开发者经过 Express
中间件的特性,开发了各类功能的中间件,用来处理某些响应以及给请求对象 req
、响应对象 res
添加属性或方法,咱们接下来就经过分析经常使用的 body-parser
中间件的原理来了解如何开发 Express
中间件,若是想了解更多 Express
内部封装原理能够看 《Express 源码分析及简易封装》。express
想刨析一个中间件的原理,首先应该从使用入手,在足够了解用法的基础上去分析,如今搭建一个简易的 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
中间件的做用是给 req
添加属性 body
,值为对象,以键值对的形式存储请求体中的参数;body-parser
只处理 POST
请求;body-parser
模块导出一个对象,上面有两个方法 urlencoded
和 json
,分别处理表单提交和 json
格式的请求体参数。在实现以前咱们先分析一下两个方法,首先都须要先读取请求体中的内容,数据传输的类型为 Buffer,转换成字符串后会根据提交方式不一样而致使请求体中的内容是查询字符串或者是 json
字符串的区别。app
当解析失败时都须要作错误处理,当不是 POST
请求时都须要向下执行其余中间件,而最核心的事就是把请求体中的数据转换成对象挂在 req.body
上。框架
使用的转换数据的方法不一样是惟一的区别,能区分二者的就是请求头 Content-Type
的值,所以咱们能够把全部的公共逻辑抽取出来用一个 acceptPost
函数来执行。异步
咱们下面建立本身的 body-parser
模块,防止命名冲突,咱们的模块命名为 my-body-parser
,处理参数须要使用 querystring
和 qs
两个模块,其中 qs
是第三方模块,使用前需安装。async
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
。
为了兼容 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(); } } }
// 处理表单提交的方法 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
,在执行异步代码时能够等待。