Koa principle and packaging

Related articles
most basic
implement a simple koa2 framework
to implement a simple version of KOA
KOA line and practice their hand

Koa source only four js file

  • application.js: Simple encapsulation http.createServer () and integrated context.js
  • context.js: proxy and integrate request.js and response.js
  • request.js: native req based package with better
  • response.js: native res based package with better

If we want a package Koa,
need to implement the use loading middleware
Next next intermediate piece and is annular,
middleware promise of

ctx => corresponding to a common body (read-write) / url (read only) / method (read only)

// request.js
const request = {
  get url() {
    return this.req.url;
  },
  set url(val) {
    this.req.url = val;
  }
};

module.exports = request;
// response.js
const response = {
  get body() {
    return this._body;
  },
  set body(data) {
    this._body = data;
  },
  get status() {
    return this.res.statusCode;
  },
  set status(statusCode) {
    if (typeof statusCode !== 'number') {
      throw new Error('statusCode 必须为一个数字');
    }
    this.res.statusCode = statusCode;
  }
};

module.exports = response;
// context.js
const context = {
  get url() {
    return this.request.url;
  },
  set url(val) {
    this.request.url = val;
  },
  get body() {
    return this.response.body;
  },
  set body(data) {
    this.response.body = data;
  },
  get status() {
    return this.response.statusCode;
  },
  set status(statusCode) {
    if (typeof statusCode !== 'number') {
      throw new Error('statusCode 必须为一个数字');
    }
    this.response.statusCode = statusCode;
  }
};
module.exports = context;
const Emitter = require('events');
const http = require('http');

// 引入 context request, response 模块
const context = require('./context');
const request = require('./request');
const response = require('./response');

class Application extends Emitter {
  /* 构造函数 */
  constructor() {
    super();
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    // 保存所有的中间件函数
    this.middlewares = [];
  }
  // 开启 http server 并且传入参数 callback
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  use(fn) {
    // this.callbackFunc = fn;
    // 把所有的中间件函数存放到数组里面去
    this.middlewares.push(fn);
    return this;
  }
  callback() {
    return (req, res) => {

      // 创建ctx
      const ctx = this.createContext(req, res);
      // 响应内容
      const response = () => this.responseBody(ctx);

      // 响应时 调用error函数
      const onerror = (err) => this.onerror(err, ctx);

      //调用 compose 函数,把所有的函数合并
      const fn = this.compose();
      return fn(ctx).then(response).catch(onerror);
    }
  }
  /**
     监听失败,监听的是上面的catch
   */
  onerror(err) {
    if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));

    if (404 == err.status || err.expose) return;
    if (this.silent) return;

    const msg = err.stack || err.toString();
    console.error();
    console.error(msg.replace(/^/gm, '  '));
    console.error();
  }
  /*
   构造ctx
   @param {Object} req实列
   @param {Object} res 实列
   @return {Object} ctx实列
  */
  createContext(req, res) {
    // 每个实列都要创建一个ctx对象
    const ctx = Object.create(this.context);
    // 把request和response对象挂载到ctx上去
    ctx.request = Object.create(this.request);
    ctx.response = Object.create(this.response);
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }
  /*
   响应消息
   @param {Object} ctx 实列
  */
  responseBody(ctx) {
    const content = ctx.body;
    if (typeof content === 'string') {
      ctx.res.setHeader('Content-Type', 'text/pain;charset=utf-8')
      ctx.res.end(content);
    } else if (typeof content === 'object') {
      ctx.res.setHeader('Content-Type', 'text/json;charset=utf-8')
      ctx.res.end(JSON.stringify(content));
    }
  }
  /*
   把传进来的所有的中间件函数合并为一个中间件
   @return {function}
  */
   compose(){
      let middlewares = this.middlewares
      return function(ctx){
        return dispatch(0)
        function dispatch(i){
           let fn = middlewares[i]
           if(!fn){
              return Promise.resolve()
           }
           return Promise.resolve(fn(ctx, function next(){
              return dispatch(i+1)
           }))
        }
      }
    } 
}

module.exports = Application;
// 使用
const testKoa = require('./application');
const app = new testKoa();

app.use((ctx) => {
  str += 'hello world'; // 没有声明该变量, 所以直接拼接字符串会报错
  ctx.body = str;
});

app.on('error', (err, ctx) => { // 捕获异常记录错误日志
  console.log(err);
});

app.listen(3000, () => {
  console.log('listening on 3000');
});

Optimization
If there is a middleware wrote two next, will be performed twice, need to determine the length of the total number of executions and middleware next, if not the same, it is necessary error

Onion ring [] What are the benefits
above onion rings may not understand, a simple version of the

var arr = [function(next){
   console.log(1)
   next()
   console.log(2)
},function(next){
   console.log(3)
   next()
   console.log(4)
}]
var i = 0;
function init(){
   arr[i](function(){
    i++
    if(arr[i]){
       init()
    }
   })
}
init()
// 1342

Why is 1342
the code above to make a break to know

// 这样应该看得懂吧
function(){
   console.log(1)
   var next = function(){
      console.log(3)
      var next = ...
      console.log(4)
   }
   next()
   console.log(2)
}

Not express the frame previous design, the entire request is a response to the end of the chain structure, a need to modify the response of the plug into the final surface, but there is a ring-shaped design, as long as the code to be modified in response to execution of the next write line after , and also for developers, the data acquisition request, the request to modify the data, next, search the database, in response to body

File Access Middleware

module.exports = (dirPath = "./public") => {
    return async (ctx, next) => {
        if (ctx.url.indexOf("/public") === 0) {
            // public开头 读取文件
            const url = path.resolve(__dirname, dirPath);
            const fileBaseName = path.basename(url);
            const filepath = url + ctx.url.replace("/public", ""); 
                console.log(filepath);
            // console.log(ctx.url,url, filepath, fileBaseName) 
            try {
                stats = fs.statSync(filepath);
                if (stats.isDirectory()) {
                    const dir = fs.readdirSync(filepath);
                    const ret = ['<div style="padding-left:20px">'];
                    dir.forEach(filename => {
                        console.log(filename);
                        // 简单认为不带小数点的格式,就是文件夹,实际应该用statSync 
                        if (filename.indexOf(".") > -1) {
                            ret.push(
                                `<p><a style="color:black" href="${
                                ctx.url
                                }/${filename}">${filename}</a></p>`
                            );
                        } else {
                            // 文件
                            ret.push(
                                `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
                            );
                        }
                    });
                    ret.push("</div>");
                    ctx.body = ret.join("");
                } else {
                    console.log("文件");
                    const content = fs.readFileSync(filepath);
                    ctx.body = content;
                }
            } catch (e) {
                // 报错了 文件不存在
                ctx.body = "404, not found";
            }
        } else {
            // 否则不是静态资源,直接去下一个中间件
            await next();
        }
    }
}

// 使用
const static = require('./static') 
app.use(static(__dirname + '/public'));

Route Middleware

class Router {
    constructor() {
        this.stack = [];
    }
    // 每次定义一个路由,都注册一次
    register(path, methods, middleware) {
        let route = { path, methods, middleware }
        this.stack.push(route);
    }
    // 现在只支持get和post,其他的同理 
    get(path, middleware) {
        this.register(path, 'get', middleware);
    }
    post(path, middleware) {
        this.register(path, 'post', middleware);
    }
      //调用
    routes() {
        let stock = this.stack;
        return async function (ctx, next) {
            let currentPath = ctx.url;
            let route;
            for (let i = 0; i < stock.length; i++) {
                let item = stock[i];
                if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
                    // 判断path和method
                    route = item.middleware; break;
                }
            }
            if (typeof route === 'function') {
                route(ctx, next);
                return;
            }
            await next();
        };
    }
}

module.exports = Router;

// 使用
const Koa = require('Koa')
const Router = require('./router')
const app = new Koa()
const router = new Router();
router.get('/index', async ctx => { ctx.body = 'index page'; });
router.get('/post', async ctx => { ctx.body = 'post page'; });
router.get('/list', async ctx => { ctx.body = 'list page'; });
router.post('/index', async ctx => { ctx.body = 'post page'; });
// 路由实例输出父中间件 
app.use(router.routes());

Next mongodb use of plug-mongoose

Guess you like

Origin www.cnblogs.com/pengdt/p/12072519.html