狭义中间件-context 代理
koa-view 实现
初始化实例代理上下文 context 实现
前言
狭义中间件区别于请求/响应拦截的另一种方式是上下文 context
代理。
上下文 context
代理分成两种
- 实例代理上下文
context
- 请求过程代理上下文
context
这里先将第一种代理方式——实例代理上下文 context
实现步骤,实例代理的比较有代表性的中间件是官方提 koa-ejs 中间件,把渲染的方法挂载在 Koa
实例 app
的 app.context
属性中。
常见化实例代理上下文 context
实现步骤
- 初始化一个 Koa 实例
let app = new Koa()
- 将需要的属性或者方法 demo 挂载在
app.context
上,app.context.demo
- 在
app.use()
中间件直接使用ctx.demo
方法或属性
这里我们实现最简单的模板渲染中间件 view
,模仿 koa-ejs
的基本能力。
实现步骤
view
的实现步骤
- step 01 初始化一个 Koa 实例
let app = new Koa()
- step 02 将需要的属性或者方法
view
挂载在app.context
上,app.context.view
- step 03 在
app.use()
中间件直接使用ctx.view
方法或属性渲染模板
实现源码
解读
const path = require('path');
const fs = require('fs');
function view(app, opts = {}) {
const { baseDir = '' } = opts;
app.context.view = function (page = '', obj = {}) {
let ctx = this;
let filePath = path.join(baseDir, page);
if (fs.existsSync(filePath)) {
let tpl = fs.readFileSync(filePath, 'binary');
ctx.body = tpl;
} else {
ctx.throw(404);
}
};
}
module.exports = view;
使用
const Koa = require('koa');
const path = require('path');
const view = require('./index');
const app = new Koa();
view(app, {
baseDir: path.join(__dirname, 'views')
});
app.use(async ctx => {
await ctx.view(`${ctx.path}.html`, {
title: 'index page'
});
});
app.listen(3000, () => {
console.log('[demo] view is starting at port 3000');
});
koa-jsonp 实现
初始化时候,实例代理上下文 context 实现
前言
实例代理的还有另外比较有代表性的中间件是官方提供 koa-safe-jsonp
中间件,把 jsonp 的方法挂载在Koa
实例app
的app.context
属性中。
常见实例代理上下文 context 实现步骤
- 初始化一个
Koa
实例let app = new Koa()
- 将需要的属性或者方法
demo
挂载在app.context
上,app.context.demo
- 在
app.use()
中间件直接使用ctx.demo
方法或属性
这里我们实现最简单的模板渲染中间件 jsonp
,模仿koa-safe-jsonp
的基本能力。
实现步骤
jsonp
的实现步骤
- step 01 初始化一个
Koa
实例let app = new Koa()
- step 02 将需要的属性或者方法
jsonp
挂载在app.context
上,app.context.jsonp
- step 03 在
app.use()
中间件直接使用ctx.jsonp
方法或属性渲染模板 - step 04 当前请求响应要返回 jsonp 数据时候
ctx.body = ctx.jsonp(result)
实现源码
demo 源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-05-02
解读
function jsonp(app, opts = {}) {
let callback = opts.callback || 'callback';
app.context.jsonp = function (obj = {}) {
let ctx = this;
if (Object.prototype.toString.call(obj).toLowerCase() === '[object object]') {
let jsonpStr = `;${callback}(${JSON.stringify(obj)})`;
// 用text/javascript,让请求支持跨域获取
ctx.type = 'text/javascript';
// 输出jsonp字符串
ctx.body = jsonpStr;
} else {
ctx.throw(500, 'result most be a json');
}
};
}
module.exports = jsonp;
使用
const Koa = require('koa');
const jsonp = require('./index');
const app = new Koa();
jsonp(app, {});
app.use(async ctx => {
await ctx.jsonp({
data: 'this is a demo',
success: true
});
});
app.listen(3000, () => {
console.log('[demo] jsonp is starting at port 3000');
});
koa-bodyparser 实现
前言
狭义中间件的上下文代理,除了在实例化 let app = new Koa()
的时候将属性或者方法挂载到app.context
中,供后续中间件使用。另外一种方式是在请求过程中在顶端中间件(一般在第一个中间件)使用,把数据或者方法挂载代理到ctx
供下游中间件获取和使用。
这里 请求代理上下文实现 最代表性是官方提供的koa-bodyparser
中间件,这里基于官方原版用最简单的方式实现koa-bodyparser
最简单功能。
常见请求代理上下文context实现过程
- 请求代理ctx
- 直接app.use()
- 在请求过程中过载方法或者数据到上下文
ctx
- 一般在大部分中间件前加载,供下游中间件获取挂载的数据或方法
实现步骤
- step 01
app.use()
在中间件最顶端 - step 02 拦截post请求
- step 03 等待解析表单信息
- step 04 把表单信息代理到ctx.request.body上
- step 05 下游中间件都可以在ctx.request.body中获取表单数据
实现源码
demo源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-05-03
依赖
请求体数据流解析方法
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 listeners
stream.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) {
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;
}
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 {
// 其他请求显示404
ctx.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>