请求代理上下文context实现
狭义中间件的上下文代理,除了在实例化 let app = new Koa()
的时候将属性或者方法挂载到app.context
中,供后续中间件使用。另外一种方式是在请求过程中在顶端中间件(一般在第一个中间件)使用,把数据或者方法挂载代理到ctx
供下游中间件获取和使用。
这里 请求代理上下文实现 最代表性是官方提供的koa-bodyparser
中间件,这里基于官方原版用最简单的方式实现koa-bodyparser
最简单功能。
常见请求代理上下文context实现过程
ctx
app.use()
在中间件最顶端demo源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-05-03
## 安装依赖npm i## 执行 demonpm run start## 最后启动chrome浏览器访问## http://127.0.0.1:3000
请求体数据流解析方法
module.exports = readStream;function readStream(req) {return new Promise((resolve, reject) => {try {streamEventListen(req, (data, err) => {if (data && !isError(err)) {resolve(data);} else {reject(err);}});} catch (err) {reject(err);}});}function isError(err) {return Object.prototype.toString.call(err).toLowerCase() === '[object error]';}function streamEventListen(req, callback) {let stream = req.req || req;let chunk = [];let complete = false;// attach listenersstream.on('aborted', onAborted);stream.on('close', cleanup);stream.on('data', onData);stream.on('end', onEnd);stream.on('error', onEnd);function onAborted() {if (complete) {return;}callback(null, new Error('request body parse aborted'));}function cleanup() {stream.removeListener('aborted', onAborted);stream.removeListener('data', onData);stream.removeListener('end', onEnd);stream.removeListener('error', onEnd);stream.removeListener('close', cleanup);}function onData(data) {if (complete) {return;}if (data) {chunk.push(data.toString());}}function onEnd(err) {if (complete) {return;}if (isError(err)) {callback(null, err);return;}complete = true;let result = chunk.join('');chunk = [];callback(result, null);}}
const readStream = require('./lib/read_stream');let strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;let jsonTypes = ['application/json'];let formTypes = ['application/x-www-form-urlencoded'];let textTypes = ['text/plain'];function parseQueryStr(queryStr) {let queryData = {};let queryStrList = queryStr.split('&');for (let [ index, queryStr ] of queryStrList.entries()) {let itemList = queryStr.split('=');queryData[ itemList[0] ] = decodeURIComponent(itemList[1]);}return queryData;}function bodyParser(opts = {}) {return async function(ctx, next) {// 拦截post请求if (!ctx.request.body && ctx.method === 'POST') {// 解析请求体中的表单信息let body = await readStream(ctx.request.req);let result = body;if (ctx.request.is(formTypes)) {result = parseQueryStr(body);} else if (ctx.request.is(jsonTypes)) {if (strictJSONReg.test(body)) {try {result = JSON.parse(body);} catch (err) {ctx.throw(500, err);}}} else if (ctx.request.is(textTypes)) {result = body;}// 将请求体中的信息挂载到山下文的request 属性中ctx.request.body = result;}await next();};}module.exports = bodyParser;
const Koa = require('koa');const fs = require('fs');const path = require('path');const body = require('../index');const app = new Koa();app.use(body());app.use(async(ctx, next) => {if (ctx.url === '/') {// 当GET请求时候返回表单页面let html = fs.readFileSync(path.join(__dirname, './index.html'), 'binary');ctx.body = html;} else if (ctx.url === '/post' && ctx.method === 'POST') {// 当POST请求的时候,解析POST表单里的数据,并显示出来ctx.body = ctx.request.body;} else {// 其他请求显示404ctx.body = '<h1>404!!! o(╯□╰)o</h1>';}await next();});app.listen(3000, () => {console.log('[demo] is starting at port 3000');});
<html><head><title>example</title></head><body><div><p>form post demo</p><form method="POST" action="/post"><span>data</span><textarea name="userName" ></textarea><br/><button type="submit">submit</button></form></div></body></html>