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');
})
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容