KOA
express大而全(es5,处理异步基于事件)。KOA基于generator,async+await。egg是基于KOA封装的。
1. ctx
包含req,res,request,response。
-
request
包装了req中的属性和方法,包含req。
-
response
包装了res中的属性和方法,包含res。
-
set()
设置响应头。
-
body
如果有值则把该值返回客户端。
2. app
-
app.use(callback(ctx,next))
next指代下一个use中注册的方法。洋葱模型。
app.listen
app.on
3. KOA简易版
//1)application.js
const http = require('http');
const context = require('./context')
const request = require('./request')
const response = require('./response');
const EventEmitter = require('events');
module.exports = class extends EventEmitter {
constructor() {
super()
this.context = Object.create(context);
this.context.request = this.request = Object.create(request);
this.context.response = this.response = Object.create(response);
this.midleWares = [];
}
createContext(req, res) {
this.context.request.req = this.context.req = req;
this.context.response.res = this.context.res = res;
return this.context;
}
use(func) {
this.midleWares.push(func);
}
handleRequest(req, res) {
let cxt = this.createContext(req, res);
this.compose().then(() => {
if (this.context.body) {
res.end(this.context.body + '');
} else {
res.end('not found');
}
}).catch(e => {
this.emit('error', e);
})
}
async compose() {
const next = (func, i = 0) => () => {
if (++i > 1) throw new Error('next() called multiple times');
return func();
}
let r = this.midleWares.reduceRight((a, b) => b.bind(null, this.context, next(a)), () => { });
return Promise.resolve(r());
/* let i = -1;
const next = (index) => {
let midle = this.midleWares[index];
if (i >= index) throw new Error('next() called multiple times');
i = index;
if (!midle) return Promise.resolve();
return midle(this.context, next.bind(null, index + 1));
}
return next(0) */
}
listen() {
http.createServer(this.handleRequest.bind(this)).on('error', (err) => {
this.emit('error', err);
}).listen(...arguments);
}
}
//2)context
let context = {
get url() {
return this.req.url;
}
}
module.exports = context;
//3)request.js
let request = {
get url() {
return this.req.url
}
}
module.exports = request;
//4)response.js
let response = {
}
module.exports = response;
4. KOA中间件和插件
KOA的第三方插件,需要安装。可以给ctx扩展一些属性,决定是否向下执行等。中间件是一个返回async函数的函数。
-
bodyParser
koa-bodyparser。会把解析的请求体挂载到
ctx.request.body
。const bodyParser = () => async (ctx, next) => { await new Promise((resolve, reject) => { let arr = [] ctx.req.on('data', (chunk) => { arr.push(chunk) }); ctx.req.on('end', () => { ctx.request.body = querystring.parse(Buffer.concat(arr).toString()); resolve(); }) }) await next(); }
-
static
koa-static。
const static = (dirname) => async (ctx, next) => { let pathname = path.join(dirname, ctx.path); try { let statObj = await fs.stat(pathname); if (statObj.isDirectory()) { pathname = path.join(pathname, 'index.html'); } await fs.access(pathname); ctx.body = await fs.readFile(pathname, 'utf-8'); } catch (e) { await next(); } }
-
Router
koa-router。
//prefix:增加前缀 const router=new Router({prefix:pathname}); //path:路径参数、* router.get(pathname,callback(ctx,next)) //装载路由。allowMethods:如果方法不存在就报405。 app.use(router.routes()).use(router.allowMethods()); //注册二级路由 rootRouter.use(pathname,userRouter.routes()); //Router简易版 class Layer { constructor(method, pathname, callback) { this.method = method; this.pathname = pathname; this.callback = callback; } match(method, pathname) { return this.method === method && this.pathname === pathname } } class Router { constructor(options = {}) { this.prefix = options.prefix || ''; this.layers = [] } routes() { return async (ctx, next) => { const method = ctx.method; const pathname = ctx.path; const layers = this.layers.filter(layer => layer.match(method, pathname)).map(layer => layer.callback); await this.compose(layers, ctx, next); } } compose(layers, ctx, next) { const func = layers.reduceRight((a, b) => { return b.bind(null, ctx, a); }, next); return Promise.resolve(func()); } allowedMethods() { return async (ctx, next) => { const method = ctx.method; const isAllow = this.layers.some(layer => layer.method === method); if (!isAllow) { ctx.res.statusCode = '405'; ctx.body = 'Method Not Allowed'; } else { await next(); } } } } ['GET', 'POST', 'DELETE', 'PUT', 'OPTIONS'].forEach(method => { Router.prototype[method.toLowerCase()] = function (pathname, callback) { this.layers.push(new Layer(method, this.prefix + pathname, callback)); } })
-
views
koa-views。
//用法 app.use(views(dirname,{ map:{ html:'ejs' } }); router.get('/',async (ctx)=>{ //会把结果挂载到ctx.body上 await ctx.render(pathname,obj) }); //views 解析ejs const views = (dirname, options) => async (ctx, next) => { const pathname = ctx.path; try { ctx.render = async (pathname, data) => { await new Promise((resolve, reject) => { ejs.renderFile(path.join(dirname, pathname), data, {}, (err, html) => { ctx.body = html; resolve(); }); }) } } catch (e) { } await next(); }
-
koa-generator
可以快速构建koa项目。
npm i koa-generator -g //-e ejs koa2 -e koa-project
5. cookie
ctx.cookie.get(key)
-
ctx.cookie.set(key,value,options)
-
signed
签名。用的sh1算法把key和value一起加密,需要设置app.keys=[secret]。secret:私钥。
js //加密的原理: crypto.createHmac('sha1',secret).update('name=xxx').digest('base64');
-
6. session
需要用到koa-session。
-
ctx.session[key]=value
设置session。
//权限校验
const Koa = require('koa');
const session = require('koa-session');
const Router = require('koa-router');
const static = require('koa-static');
const bodyparser = require('koa-bodyparser');
const router = new Router();
const app = new Koa();
app.keys = ['12345678'];
app.use(bodyparser());
app.use(async (ctx, next) => {
if (ctx.url === '/') {
if (ctx.session['username']) {
return ctx.redirect('/personal');
} else {
return await next();
}
}
if (ctx.url === '/personal') {
return ctx.body = ctx.session['username'];
}
next();
})
router.post('/login', async (ctx) => {
const { username, password } = ctx.request.body;
if (username === password) {
ctx.session.username = username;
ctx.redirect('/personal');
}
});
app.use(session({
httpOnly: true,
maxAge: 30000
}, app));
app.use(router.routes());
app.use(static(__dirname));
app.listen(3000, () => {
console.log('server start 3000');
})