简单实现属于的自己的Koa

162 阅读2分钟

前置

开始之前,先来看一个例子:

const Koa = require('koa');
const app = new Koa();
app.use(async (ctx,next)=>{
  console.log('fn1');
  await next();
  console.log('end fn1');
})

app.use(async (ctx,next)=>{
  console.log('fn2')
  await next()
  console.log('end fn2')
}
app.use(async (ctx,next)=>{
  console.log('fn3')
}
app.listen(3000);

上面的代码打印的是什么内容?

中间件

上面的代码,体现了Koa源码中最重要的部分,即中间件,每次use也就推入一个中间件
打印的顺序也反映了Koa的中间件模型,即洋葱圈模型。 洋葱圈模型

  • 每个中间件执行过程中遇到next便执行下一个中间件,直到执行到最后一个中间件,然后往回执行剩余代码。

  • 中间件代码(递归实现)

/*  文件名:compose.js */
function compose(middlewares) {  // middlewares中间件列表
    return function (ctx) {  // ctx: request,response组合的context
        // 定义一个派发器
        function dispatch(i) {
            // 获取当前中间件
            const fn = middlewares[i]
            try {
                return Promise.resolve(
                    // 通过 i + 1 获取下一个中间件,传递给 next 参数
                    fn(ctx, function next(){  // next关键
                    	return dispatch(i+1)
                    })
                )
            } catch (err) {
                // fn不存在自动捕获错误
                return Promise.reject(err)
            }
        }
        // 开始派发第一个中间件
        return dispatch(0)
    }
}

module.exports = compose;

context, request, response

  • 基于这些为原型定义真实的ctx,req,res
/* proto.js */
const Cookies = require('cookies');
const COOKIES = Symbol('context#cookies');

// 原型基础属性定义
const request = {
  get header() {
    return this.req.headers;
  },
  set header(val) {
    this.req.headers = val;
  },
  get headers() {
    return this.req.headers;
  },
  set headers(val) {
    this.req.headers = val;
  },
  get url() {
    return this.req.url;
  },
  set url(val) {
    this.req.url = val;
  }
  /* 剩余其他内容  */
}

// 原型基础属性定义
const response = {
  get header() {
    const { res } = this;
    return typeof res.getHeaders === 'function'
      ? res.getHeaders()
      : res._headers || {}; // Node < 7.7
  },
  get headers() {
    return this.header;
  },
  get status() {
    return this.res.statusCode;
  },  
  get header() {
    const { res } = this;
    return typeof res.getHeaders === 'function'
      ? res.getHeaders()
      : res._headers || {}; // Node < 7.7
  },
  get headers() {
    return this.header;
  },
  get status() {
    return this.res.statusCode;
  },
  get body(){
    return this._body
  },
  set body(val){
    this._body = val
  }
  /* 剩余其他内容  */
}

const context = {
  toJSON() {
    return {
      request: this.request.toJSON(),
      response: this.response.toJSON(),
      app: this.app.toJSON(),
      originalUrl: this.originalUrl,
      req: '<original node req>',
      res: '<original node res>',
      socket: '<original node socket>'
    };
  },
  get cookies() {
    if (!this[COOKIES]) {
      this[COOKIES] = new Cookies(this.req, this.res, {
        keys: this.app.keys,
        secure: this.request.secure
      });
    }
    return this[COOKIES];
  },
  set cookies(_cookies) {
    this[COOKIES] = _cookies;
  }
  /* 剩余其他内容  */
};
module.exports = {
  request,
  response,
  context
}

开始构建真正的Koa构建

const http = require('http');
const compose = require("./compose.js");
const {
  request,
  response,
  context
} = require("./proto.js")

class Application {
  constructor(){
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    this.middlewares = [];
  }
  use(fn){
    this.middlewares.push(fn);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  
  /* createServer内容 */
  callback() {
    const fn = compose(this.middleware);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);  // 就是fn(ctx) 执行中间件
    };
    return handleRequest;
  }

  handleRequest(ctx, fnMiddleware) {
    /* 错误处理机制。。。 */
    return fnMiddleware(ctx);
  }

  /* 创建真实的context */
  createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.state = {};
    return context;
  }
}
module.exports = Application