Koa框架

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');
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容