node koa源码实现

103 阅读2分钟

预备知识

getter/setter 方法包装数据的访问

const obj = {
    info : { name : "jason"},
    get name(){
        return this.info.name;
    },
    set name(val){
        console.log("开始设置新内容",val)
        this.info.name = val
    }
}
console.log(obj.name)
obj.name = "xxx"
console.log(obj.name)

koa 框架实现

整体代码结构

image.png

myKoa源码实现

//myKoa.js //主代码
//context.js //记录上下文 封装了 req 和 res 
//request.js //重写http原来的request对象
//response.js //重写http原来的response对象
//app.js //程序入口


//myKoa.js
const http = require("http")
const context = require('./context')
const request = require('./request')
const response = require('./response')

class myKoa {
    constructor(){
        this.middlewares = []
    }
    listen(...args){
        const server = http.createServer(async (req, res) => {

            // 创建上下文
            const ctx = this.createCTX(req, res)
            const fn = this.compose(this.middlewares)
            await fn(ctx)
            // this.callback(req,res)
            // this.callback(ctx)
            res.end(ctx.body)
        })
        server.listen(...args)
    } 
    use(middleware){
        this.middlewares.push(middleware)
    }
    createCTX(req, res) {
        const ctx = Object.create(context)
        ctx.request = Object.create(request)
        ctx.response = Object.create(response)

        ctx.req = ctx.request.req = req
        ctx.res = ctx.response.res = res
        return ctx
    }
    compose(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 = myKoa
 
//context.js
module.exports = {
    get url() {
        return this.request.url
    },
    get body() {
        return this.response.body
    },
    set body(val){
        this.response.body = val
    },
    get method() {
        return this.request.method
    }
}

//request.js
module.exports ={
    get url(){
        return this.req.url
    },

    get method(){
        return this.req.method.toLowerCase()
    }
}

//response.js
module.exports = {
    get body(){
        return this._body
    },
    set body(val){
        this._body = val
    }
}



//程序测试入口 app.js
const myKoa = require('./myKoa')
const app = new myKoa()
const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000));
app.use(async (ctx, next) => {
    ctx.body = "1";
    setTimeout(() => {
        ctx.body += "2";
    }, 2000);
    await next();
    ctx.body += "3";
});

app.use(async (ctx, next) => {
    ctx.body += "4";
    await delay();
    await next();
    ctx.body += "5";
});

app.use(async (ctx, next) => {
    ctx.body += "6";
});
app.listen(3000)

koa-router路由的实现

  1. 使用策略模式/决策模式, 原理:注册表匹配读取方式
  2. 保存全局的数组列表对象stack对象,
  3. 每次注册记录对象route:请求方式,地址,对应方法
  4. 每次响应匹配对应的 请求方式,地址,执行对应方法
//router.js
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;
  
//测试代码app.js
const myKoa = require('./myKoa')
const app = new myKoa()

const Router = require('./router')
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());
app.listen(3000)

koa-static静态资源响应实现

文件结构

image.png

// static.js
const fs = require("fs");
const path = require("path");

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
          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();
    }
  };
};


//测试代码app.js
const myKoa = require('./myKoa')
const app = new myKoa()

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

app.listen(3000)

访问地址