前言
前面我们已经把【裸配】 Koa 学习了一下,学完了以后好像并没有什么好用的东西,然而这玩意就像一个巨大的平台,容易集成各种小插件,来达到各种各样的功能。接下来我们学习一个 koa-bodyparser 这歌短小精悍的库!
HelloWorld
当我的 hello world 如此简单的时候,我想发一个 POST 请求,那么我怎么拿到 POST 请求的参数呢,我们 ctx.request.body 是空的,Koa 原声的框架并没有帮我们解析 POST 的请求数据。所以我们就需要加入 koa-bodyparser 中间件,这样我们就可以通过 ctx.request.body 拿到数据了。
1 2 3 4 5 6 7 const Koa = require('koa'); const app = new Koa(); app.use(async ctx => { // ctx.body = ctx.query.param; console.log( ctx.request.body) }); app.listen(3000);
首先安装这个库:
1 npm install koa-bodyparser --save
然后再代码中加入这个中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 const Koa = require('koa'); const bodyParser = require('koa-bodyparser') const app = new Koa(); app.use(bodyParser()); app.use(async ctx => { ctx.body = ctx.request.body.param; console.log(ctx.request.body) }); app.listen(3000);
这时候 ctx.request.body 便是一个解析好的对象了,直接取对象的属性就可以了
题外话
原声的 NodeJS 通过 createServer 也是没有办法直接取到 post 的参数的,还是需要做一些读取数据的操作才可以
源码解析
bodyParser
作为一个中间件,需要返回一个 async 函数,在这个函数的最后调用 next(),关键部分是 parseBody 函数,将 request 进行解析,并将结果返回赋值给 ctx.request.body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 return async function bodyParser(ctx, next) { if (ctx.request.body !== undefined) return await next(); if (ctx.disableBodyParser) return await next(); try { const res = await parseBody(ctx); ctx.request.body = 'parsed' in res ? res.parsed : {}; if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw; } catch (err) { if (onerror) { onerror(err, ctx); } else { throw err; } } await next(); };
parseBody
ctx.request.is 可以检查 request 【Content-Type】 请求的类型是否是当中的一个,一般情况下以 form 居多,即 application/x-www-form-urlencoded
1 2 3 4 5 6 7 8 9 10 11 12 async function parseBody(ctx) { if (enableJson && ((detectJSON && detectJSON(ctx)) || ctx.request.is(jsonTypes))) { return await parse.json(ctx, jsonOpts); } if (enableForm && ctx.request.is(formTypes)) { return await parse.form(ctx, formOpts); } if (enableText && ctx.request.is(textTypes)) { return await parse.text(ctx, textOpts) || ''; } return {}; }
主要是 raw(inflate(req), opts) 方法,将请求的参数转化为一个 string
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 module.exports = async function(req, opts) { req = req.req || req; opts = utils.clone(opts); const queryString = opts.queryString || {}; // keep compatibility with qs@4 if (queryString.allowDots === undefined) queryString.allowDots = true; // defaults const len = req.headers['content-length']; const encoding = req.headers['content-encoding'] || 'identity'; if (len && encoding === 'identity') opts.length = ~~len; opts.encoding = opts.encoding || 'utf8'; opts.limit = opts.limit || '56kb'; opts.qs = opts.qs || qs; const str = await raw(inflate(req), opts); try { const parsed = opts.qs.parse(str, queryString); return opts.returnRawBody ? { parsed, raw: str } : parsed; } catch (err) { err.status = 400; err.body = str; throw err; } };
raw
读取 body 数据,
1 2 3 4 5 6 7 8 9 function getRawBody (stream, options, callback) { return new Promise(function executor (resolve, reject) { readStream(stream, encoding, length, limit, function onRead (err, buf) { if (err) return reject(err) resolve(buf) }) }) }
选取了 onData 函数,可以接受流数据,以字符串形式,结束后直接返回 字符串即可,数据为 buffer,使用 buffer.toString() 就可以转化为字符串。
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 function readStream (stream, encoding, length, limit, callback) { // attach listeners stream.on('aborted', onAborted) stream.on('close', cleanup) stream.on('data', onData) stream.on('end', onEnd) stream.on('error', onEnd) function onData (chunk) { if (complete) return received += chunk.length if (limit !== null && received > limit) { done(createError(413, 'request entity too large', { limit: limit, received: received, type: 'entity.too.large' })) } else if (decoder) { buffer += decoder.write(chunk) } else { buffer.push(chunk) } } function onEnd (err) { if (complete) return if (err) return done(err) if (length !== null && received !== length) { done(createError(400, 'request size did not match content length', { expected: length, length: length, received: received, type: 'request.size.invalid' })) } else { var string = decoder ? buffer + (decoder.end() || '') : Buffer.concat(buffer) done(null, string) } } }
在 onEnd 中返回字符串
写在最后
koa-bodyparser 不仅做了 post 的转化,还有一些可选的参数,比如说传输类型,大小限制,这些都是可以通过参数进行配置的。源码博大精深啊!