实现一个简易版的koa

698 阅读3分钟

什么是koa

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

koa的特点

koa2完全使用Promise并配合 async 来实现异步 其特点是

  • 轻量

  • 无捆绑

  • 中间件架构

  • 优雅的API设计

  • 增强的错误处理

koa 的实现

构造一个 koa 类

class KOA {
    constructor(){
        // middlewares 数组用于管理中间件函数
        this.middlewares = []
    }
    listen(...args) {
        // 服务器监听端口实现逻辑
    }
        
    // 添加中间件函数方法 
    use(middleware){
        this.middlewares.push(middleware)
    }
    createContext(req, res) {
                // 创建 context 上下文
    }
    compose(middlewares) {
        // 实现洋葱圈模型
    }
}

创建一个应用程序:

const app = new Koa();
  • KOA 类定义了 this.middlewares 数组来管理中间件函数;

  • app.listen() 方法创建了一个 HTTP 服务器;

  • app.use() 方法用于给 this.middlewares 添加中间件函数;

  • app.createContext() 方法引入上下文context的概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从而简化操作;

  • app.compose() 方法则是洋葱圈模型的实现核心;

实现 listen 方法

listen(...args) {
    const server = http.createServer(async (req, res) => {
        // 创建上下文对象
        const ctx = this.createContext(req, res);
        // 中间件合成
        const fn = this.compose(this.middlewares);
        // 执行合成函数并传入上下文
        await fn(ctx);
        res.end(ctx.body);
    })
    // 监听端口
    server.listen(...args)
}

实现 use 方法

use 方法的作用就是给 this.middlewares 添加中间件函数

use(middleware){
  // 将中间件添加到数组里
  this.middlewares.push(middleware)
}

实现 createContext 方法

构建上下文, 把res和req都挂载到ctx之上,并且在ctx.req和ctx.request.req同时保存。

createContext(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 方法

Koa中间件机制就是函数式组合概念 Compose 的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制。

compose(middlewares) {
  // 传入上下文对象
  return function (ctx) {
    // 执行第一个中间件函数
    return dispatch(0)
    // dispatch函数递归遍历 middleware,
    // 然后将 context 和 dispatch(i + 1) 传给 middleware 中的方法
    function dispatch(i) {
      let fn = middlewares[i]
      if (!fn) {
        return Promise.resolve()
      }
      return Promise.resolve(
        // 将上下文传入中间件 mid(ctx, next)
        fn(ctx, function next() {
          // 执行下一个中间件函数
          return dispatch(i + 1)
        })
      )
    }
  }
}

封装 request、response 和 ontext

// request.js
module.exports = {
  get url() {
    return this.req.url;
  },
  get method() {
    开课吧web全栈架构师
    return this.req.method.toLowerCase()
  }
};

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

// 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
  }
};

KOA 类的完整代码

const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')
class KOA {
    constructor(){
        this.middlewares = []
    }
    listen(...args) {
        const server = http.createServer(async (req, res) => {

            // 创建上下文
            const ctx = this.createContext(req, res)
            const fn = this.compose(this.middlewares)
            await fn(ctx)
            res.end(ctx.body)
        })
        server.listen(...args)
    }
    use(middleware){
      this.middlewares.push(middleware)
    }
    createContext(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 = KOA;

koa 使用

const KOA = require('./koa')
const app = new KOA()

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