body-parser 源码分析

在 node http 模块中,您只能通过 data 事件,以 buffer 的形式来获取请求体内容,node 没有提供如何解析请求body的API,body-parser 提供了这个功能。

body-parser 本质是一个处理请求 body 的中间件函数,他负责按照您给的规则解析请求body,并且将结果赋值到 req.body 属性上。

2. 简单的使用 body-parser var express = require('express') var bodyParser = require('body-parser') var app = express() // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) app.use(function (req, res) { res.setHeader('Content-Type', 'text/plain') res.write('you posted:\n') // 您可以通过req.body 来访问请求体内容 res.end(JSON.stringify(req.body, null, 2)) })

通过这个例子您可以了解到如何简单的使用 body-parser。

3. 源码分析

首先 bodyParser 的源码结构如下:

image.png

index.js:入口文件

lib:核心方法

types:该文件下的4个文件,分别用于解析对应的4个类型

json.js:将body解析为JSON对象

raw.js

text.js:将body解析为字符串

urlencoded.js:将表单数据(urlencoded编码)解析为JSON对象

read.js:读取 body 内容

1. bodyParser的导出形式

bodyParser 的定义在 index.js,这里的逻辑非常清晰:

创建一个用于解析 json 和 urlencoded 格式的中间件:bodyParser 并导出

给 bodyParser 添加 json/text/raw/urlencoded 方法

'use strict' var deprecate = require('depd')('body-parser') // 缓存 parser var parsers = Object.create(null) // 导出一个Function exports = module.exports = deprecate.function(bodyParser, 'bodyParser: use individual json/urlencoded middlewares') // JSON parser. Object.defineProperty(exports, 'json', { configurable: true, enumerable: true, get: createParserGetter('json') }) // Raw parser. Object.defineProperty(exports, 'raw', { configurable: true, enumerable: true, get: createParserGetter('raw') }) // Text parser. Object.defineProperty(exports, 'text', { configurable: true, enumerable: true, get: createParserGetter('text') }) // URL-encoded parser. Object.defineProperty(exports, 'urlencoded', { configurable: true, enumerable: true, get: createParserGetter('urlencoded') }) // 创建一个用于解析 json 和 urlencoded 格式的中间件 function bodyParser (options) { var opts = {} // exclude type option if (options) { for (var prop in options) { if (prop !== 'type') { opts[prop] = options[prop] } } } var _urlencoded = exports.urlencoded(opts) var _json = exports.json(opts) return function bodyParser (req, res, next) { _json(req, res, function (err) { if (err) return next(err) _urlencoded(req, res, next) }) } } // Create a getter for loading a parser. function createParserGetter (name) { return function get () { return loadParser(name) } } // Load a parser module. function loadParser (parserName) { var parser = parsers[parserName] if (parser !== undefined) { return parser } // this uses a switch for static require analysis switch (parserName) { case 'json': parser = require('./lib/types/json') break case 'raw': parser = require('./lib/types/raw') break case 'text': parser = require('./lib/types/text') break case 'urlencoded': parser = require('./lib/types/urlencoded') break } // store to prevent invoking require() return (parsers[parserName] = parser) } 4. text 解析流程

将 body 解析非常简单,这只需要将 buffer 转换为 string即可。 所以从最简单 text parser 开始,其他解析大体也是类似的,主要区别在于将字符串解析到特定格式的方法。比如将表单数据(urlencoded form) 解析为JSON对象。

现在您希望将 text/plain 的请求体解析为一个字符串,源码是这样的:

// 默认将 type 为 text/plain 解析为字符串 var express = require('express') var bodyParser = require('body-parser') var app = express() var port = 3000; app.use(bodyParser.text()) app.post('/text', (req, res) => res.send(req.body)) app.listen(port, () => console.log(`\nExample app listening on port ${port}!`))

当我们 curl 进行如下访操作:

$ curl -d "hello" :3000/text hello

这背后的流程是怎样的呢?

1. bodyParser.text() 中间件

由于我们使用 bodyParser.text() 中间件,所以当进行上述访问时,会访问到 lib/types/text,源码如下:

'use strict' var bytes = require('bytes') var contentType = require('content-type') var debug = require('debug')('body-parser:text') var read = require('../read') var typeis = require('type-is') // 导出 text 中间件 module.exports = text // text 中间件 定义 function text (options) { // option 是使用该中间件传入的选项 var opts = options || {} // 获取字符集 var defaultCharset = opts.defaultCharset || 'utf-8' // 是否处理压缩的body, true时body会被解压,false时body不会被处理 var inflate = opts.inflate !== false // body大小限制 var limit = typeof opts.limit !== 'number' ? bytes.parse(opts.limit || '100kb') : opts.limit // 需要处理的 content-type 类型 var type = opts.type || 'text/plain' // 用户自定义的校验函数,若提供则会被调用verify(req, res, buf, encoding) var verify = opts.verify || false if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } // create the appropriate type checking function var shouldParse = typeof type !== 'function' ? typeChecker(type) : type // 这里是核心, 不同的解析器有不同的处理方式 // text parse 很简单是因为它啥也不需要干 function parse (buf) { return buf } return function textParser (req, res, next) { // 当我们进行 POST 请求时 textParser 中间件会被调用 // 这里先判断 body 是否已经解析过了,下游会设置为 true if (req._body) { debug('body already parsed') next() return } req.body = req.body || {} // 没有请求体时不处理 // skip requests without bodies if (!typeis.hasBody(req)) { debug('skip empty body') next() return } debug('content-type %j', req.headers['content-type']) // determine if request should be parsed if (!shouldParse(req)) { debug('skip parsing') next() return } // get charset var charset = getCharset(req) || defaultCharset // read read(req, res, next, parse, debug, { encoding: charset, inflate: inflate, limit: limit, verify: verify }) } } // 获取请求字符集 function getCharset (req) { try { return (contentType.parse(req).parameters.charset || '').toLowerCase() } catch (e) { return undefined } } // content-type 检测 function typeChecker (type) { return function checkType (req) { return Boolean(typeis(req, type)) } } // 判断是否包含请求体(这个函数是从type-is包复制出来的) function hasbody (req) { return req.headers['transfer-encoding'] !== undefined || !isNaN(req.headers['content-length']) }

大概流程如下:

使用 app.use 使用中间件

客户端发起 POST 请求

进入 textParser 中间件

判断是否已经解析过(req._body = true)

判断请求是否包含请求体

判断请求体类型是否需要处理

读取请求体,解析并设置 req.body && req._body = true

进入 read 中间件(读取请求体,解析并设置 req.body && req._body = true)

2. read() 中间件(lib/read.js)

lib/types 下的4个文件,最终都会访问 lib/read.js,形式如下:

read(req, res, next, parse, debug, { encoding: charset, inflate: inflate, limit: limit, verify: verify })

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjwyf.html