likes
comments
collection
share

Koa核心源码实现(路由,中间件…)

作者站长头像
站长
· 阅读数 49

Koa深入浅出

简介

  • 定义:KoaExpress的原班开发人马使用ES2015中的新特性(主要是Generator)重新打造的新的Web框架——Koa,Koa的初衷就是彻底解决在NodeWeb开发中的异步问题,
  • 历史:Koa的发展存在Koa1.xKoa2两个阶段,两者之间的区别在于Koa2使用了ES2017async方法来处理中间件的调用(Koa1.x使用的是generator),该特性已经在v7.6.0之后的Node版本中提供原生支持。

使用例子

  • 整体步骤
    • 引入Koa模块,创建app对象
    • 挂载中间件
    • 调用listen方法监听端口
const Koa = require('Koa');
const app = new Koa();

//加载一些中间件
app.use(...);
app.use(....);
app.use(.....);

app.listen(3000);

流程简述

  1. 通过挂载的方式(app.use),将若干中间件函数推入一个数组中(middlewares)。
  2. 其中路由的挂载比较特别,先推入一个路由中间件函数,在函数内通过url和method注册路由及回调,最后根据请求信息执行对应的回调。
  3. 通过events模块监听访问路径(router.get),当请求进来时,执行middwares(异步串行compose),并在末尾返回ctx.body(语义化)。
  4. 在若干中间件函数中,需要挂载某些对象及获取某些全局的信息,因此需要全局的上下文作为媒介(ctx)
  5. 且在请求与响应时需要对应的API及信息,均挂载在对应的对象上request、response

核心源码实现(珠峰)

初始化&中间件&对象挂载

// 引入中间件版
const http = require("http");
const context = require("./utils/context");
const response = require("./utils/response");
const request = require("./utils/request");
// const composeForNext = require('./utils/middleware')
class Koa {
  constructor() {
    this.middlewares = [];
  }

  listen(...args) {
    // 开启服务,传入参数
    const server = http.createServer(async (req, res) => {
      /**
       * 为回调函数注入ctx上下文对象
       *      ctx解析
       *          1. 整合了request、response
       *          2. 通过setter、getter 优雅地获取url、method等值。
       *             通过ctx.body的方式,封装res.end()返回数据,,使代码更为可读。
       */
      const ctx = this.createContext(req, res);
      console.log("middlewares", this.middlewares);
      // 引入中间件,聚合函数,使其同步执行
      let fns = this.composeForNext(this.middlewares);
      await fns(ctx);
      res.end(ctx.body);
    });
    server.listen(...args);
  }
  use(callback) {
    console.log("callback", callback);
    // 加入回调函数组
    this.middlewares.push(callback);
  }
  createContext(req, res) {
    // 创建ctx对象
    const ctx = Object.create(context);
    // 为ctx对象注入request,当访问ctx.req时,实际是访问request内的变量
    ctx.request = Object.create(request);
    // 为ctx对象注入response,当访问ctx.req时,实际是访问response内的变量
    ctx.response = Object.create(response);

    // 层层传递,将req的值赋予request再赋予ctx
    // 通过getter、setter对部分关键值的读写进行覆盖
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }
  composeForNext(middlewares) {
    // 返回一个将函数数组同步化的函数(回调地狱)
    // 通过递归及promise,保证函数的执行顺序。
    return function (ctx) {
      // 执行递归函数
      return dispatch(0);
      function dispatch(i) {
        // 根据索引获取函数体
        let fn = middlewares[i];
        // 如果无对应函数体,即最后一个next
        if (!fn) {
          // 返回空执行承诺,有可能上一个函数是异步函数,
          // 即await的,所以要保证返回的值是promise形式的。
          return Promise.resolve();
        }
        // 如果有函数体
        return Promise.resolve(
          // 函数体执行,并且将下一个函数传入该函数体
          fn(ctx, function next() {
            // 执行下一个函数体,并返回下一个函数的执行承诺。
            // 如果不返回则该函数体不会被放置在promise里,则失去同步性。
            return dispatch(i + 1);
          })
        );
      }
    };
  }
}
module.exports = Koa;

中间件补充

  • 中间件的本质是一个函数数组。在Koa中,会为其中的函数注入两个实参(ctx,next),分别表示上下文对象以及下一个要执行的中间件函数。
  • 当有多个中间件的时候,本质上是一种嵌套调用(异步串行),就像洋葱图一样:Koa核心源码实现(路由,中间件…)

关于动态加载中间件

  • 在某些应用场景中,开发者可能希望能够动态加载中间件,例如当路由接收到某个请求后再去加载对应的中间件,但在Koa中这是无法做到的。
  • 原因其实已经在前面了,Koa 应用唯一一次加载所有中间件是在调用listen方法的时候,即使后面再调用app.use方法,也不会生效了。

context处理(上下文挂载对象)

  • 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()
    }
}
  • resonse.js
module.exports = {
  _body: "",
  get body() {
    return this._body;
  },
  set body(val) {
    this._body = val;
  },
};

Koa路由

  • koa路由本身是即为了减少判断,使用的策略模式,本质就是注册对象规则,然后匹配
class Router {
  constructor() {
    // 栈队列
    this.stack = [];
  }

  // 注册路由的具体API
  register(path, methods, middleware) {
    let route = { path, methods, middleware };
    this.stack.push(route);
  }
  // 注册get请求路由
  get(path, middleware) {
    this.register(path, "get", middleware);
  }
  // 注册post请求路由
  post(path, middleware) {
    this.register(path, "post", middleware);
  }

  // 最后路由挂载到app上,即开始监听
  route() {
    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.methods) >= 0
        ) {
          // 赋值对应的回调函数
          route = item.middleware;
          break;
        }
      }
      // 函数执行
      if (typeof route === "function") {
        route(ctx, next);
        return;
      }
    };
  }
}

转载自:https://juejin.cn/post/7240000171219304509
评论
请登录