文章目录
由于需要在 expressjs 中解析 xml ,而 expressjs 默认无法解析 xml 。所以我打算自己造一个工业级轮子,于是参考了 expressjs 中关于 json 解析方面的代码。代码分析如是。
朴素实现是监听 req 的 data 事件,将读到的数据存储在字符串变量中再进行解析。body-parser 的实现与此有不少区别。首先,代码中有很多校验信息,要根据请求头中的内容对请求体做类型、长度、字符编码等校验,以此提高安全性;其次,代码都是通过调用 raw-body 模块中的 getRawBody(stream, options, callback) 来解析请求体的,而不是直接监听 data 事件进行操作。getRawBody 函数会做请求体校验、异常处理、管道卸载等工作,比朴素的做法安全很多。 getRawBody 函数也可以完成解码功能。
body-parser 的文档和代码见: https://github.com/expressjs/body-parser 。中文注释为我的注析。这里只解析 index.js 及其调用的内容, lib/types/ 下的其他文件结构类似,忽略不析。
index.js link 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 var deprecate = require ('depd' )('body-parser' )var fs = require ('fs' ) var path = require ('path' )exports = module .exports = deprecate.function (bodyParser, 'bodyParser: use individual json/urlencoded middlewares' )var parsersDir = path.join (__dirname, 'lib' , 'types' )fs.readdirSync (parsersDir).forEach (function onfilename (filename ) { if (!/\.js$/ .test (filename)) return var loc = path.resolve (parsersDir, filename) var mod var name = path.basename (filename, '.js' ) function load ( ) { if (mod) { return mod } return mod = require (loc) } Object .defineProperty (exports , name, { configurable : true , enumerable : true , get : load }) }) function bodyParser (options ){ var opts = {} options = options || {} for (var prop in options) { if ('type' !== prop) { 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); }); } }
lib/types/json.js link 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 var bytes = require ('bytes' )var read = require ('../read' )var typer = require ('media-typer' ) var typeis = require ('type-is' )module .exports = jsonvar firstcharRegExp = /^\s*(.)/ function json (options ) { options = options || {} var limit = typeof options.limit !== 'number' ? bytes (options.limit || '100kb' ) : options.limit var inflate = options.inflate !== false var reviver = options.reviver var strict = options.strict !== false var type = options.type || 'json' var verify = options.verify || false if (verify !== false && typeof verify !== 'function' ) { throw new TypeError ('option verify must be function' ) } function parse (body ) { if (0 === body.length ) { throw new Error ('invalid json, empty body' ) } if (strict) { var first = firstchar (body) if (first !== '{' && first !== '[' ) { throw new Error ('invalid json' ) } } return JSON .parse (body, reviver) } return function jsonParser (req, res, next ) { if (req._body ) return next () req.body = req.body || {} if (!typeis (req, type)) return next () var charset = typer.parse (req).parameters .charset || 'utf-8' if (charset.substr (0 , 4 ).toLowerCase () !== 'utf-' ) { var err = new Error ('unsupported charset' ) err.status = 415 next (err) return } read (req, res, next, parse, { encoding : charset, inflate : inflate, limit : limit, verify : verify }) } } function firstchar (str ) { if (!str) return '' var match = firstcharRegExp.exec (str) return match ? match[1 ] : '' }
lib/read.js link 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 var getBody = require ('raw-body' )var iconv = require ('iconv-lite' )var onFinished = require ('on-finished' )var typer = require ('media-typer' )var zlib = require ('zlib' )module .exports = readfunction read (req, res, next, parse, options ) { var length var stream req._body = true try { stream = contentstream (req, options.inflate ) length = stream.length delete stream.length } catch (err) { return next (err) } options = options || {} options.length = length var encoding = options.encoding !== null ? options.encoding || 'utf-8' : null var verify = options.verify options.encoding = verify ? null : encoding getBody (stream, options, function (err, body ) { if (err) { if (!err.status ) { err.status = 400 } stream.resume () onFinished (req, function onfinished ( ) { next (err) }) return } if (verify) { try { verify (req, res, body, encoding) } catch (err) { if (!err.status ) err.status = 403 return next (err) } } try { body = typeof body !== 'string' && encoding !== null ? iconv.decode (body, encoding) : body req.body = parse (body) } catch (err) { if (!err.status ) { err.body = body err.status = 400 } return next (err) } next () }) } function contentstream (req, inflate ) { var encoding = req.headers ['content-encoding' ] || 'identity' var err var length = req.headers ['content-length' ] var stream if (inflate === false && encoding !== 'identity' ) { err = new Error ('content encoding unsupported' ) err.status = 415 throw err } switch (encoding) { case 'deflate' : stream = zlib.createInflate () req.pipe (stream) break case 'gzip' : stream = zlib.createGunzip () req.pipe (stream) break case 'identity' : stream = req stream.length = length break default : err = new Error ('unsupported content encoding' ) err.status = 415 throw err } return stream }
lib/types/urlencoded.js link 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 var bytes = require ('bytes' )var deprecate = require ('depd' )('body-parser' )var read = require ('../read' )var typer = require ('media-typer' )var typeis = require ('type-is' )module .exports = urlencodedvar parsers = Object .create (null )function urlencoded (options ){ options = options || {}; if (options.extended === undefined ) { deprecate ('undefined extended: provide extended option' ) } var extended = options.extended !== false var inflate = options.inflate !== false var limit = typeof options.limit !== 'number' ? bytes (options.limit || '100kb' ) : options.limit var type = options.type || 'urlencoded' var verify = options.verify || false if (verify !== false && typeof verify !== 'function' ) { throw new TypeError ('option verify must be function' ) } var queryparse = extended ? extendedparser (options) : simpleparser (options) function parse (body ) { return body.length ? queryparse (body) : {} } return function urlencodedParser (req, res, next ) { if (req._body ) return next (); req.body = req.body || {} if (!typeis (req, type)) return next (); var charset = typer.parse (req).parameters .charset || 'utf-8' if (charset.toLowerCase () !== 'utf-8' ) { var err = new Error ('unsupported charset' ) err.status = 415 next (err) return } read (req, res, next, parse, { encoding : charset, inflate : inflate, limit : limit, verify : verify }) } } function extendedparser (options ) { var parameterLimit = options.parameterLimit !== undefined ? options.parameterLimit : 1000 var parse = parser ('qs' ) if (isNaN (parameterLimit) || parameterLimit < 1 ) { throw new TypeError ('option parameterLimit must be a positive number' ) } if (isFinite (parameterLimit)) { parameterLimit = parameterLimit | 0 } return function queryparse (body ) { if (overlimit (body, parameterLimit)) { var err = new Error ('too many parameters' ) err.status = 413 throw err } return parse (body, {parameterLimit : parameterLimit}) } } function overlimit (body, limit ) { if (limit === Infinity ) { return false } var count = 0 var index = 0 while ((index = body.indexOf ('&' , index)) !== -1 ) { count++ index++ if (count === limit) { return true } } return false } function parser (name ) { var mod = parsers[name] if (mod) { return mod.parse } mod = parsers[name] = require (name) return mod.parse } function simpleparser (options ) { var parameterLimit = options.parameterLimit !== undefined ? options.parameterLimit : 1000 var parse = parser ('querystring' ) if (isNaN (parameterLimit) || parameterLimit < 1 ) { throw new TypeError ('option parameterLimit must be a positive number' ) } if (isFinite (parameterLimit)) { parameterLimit = parameterLimit | 0 } return function queryparse (body ) { if (overlimit (body, parameterLimit)) { var err = new Error ('too many parameters' ) err.status = 413 throw err } return parse (body, undefined , undefined , {maxKeys : parameterLimit}) } }