# 手写 Koa

# 启动基本服务

建立 lib 文件夹用来存放手写的 koa 代码

写一个 app.js 用来测试我们的 koa

app.js

const YdKoa = require("./lib/application");

const app = new YdKoa();

app.use(async (ctx, next) => {
  console.log("入口");
  await next();
  console.log("出口");
});

app.listen(3000, () => {
  console.log("server started");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 这是一个非常基本的启动服务的东西,包括插入中间件

接下来手写一下我们的 koa 的核心部分

application.js

const EventEmitter = require("events");
const http = require("http");

class Application extends EventEmitter {
  constructor() {
    super();
    this.middlewares = [];
  }
  use(middleware) {
    this.middlewares.push(middleware);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
  callback() {
    return (req, res) => {
      res.end("hello world");
    };
  }
}
module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 首先定义导出一个类 Application
  • node 的所有事件都基于 events,所以我们的类要继承自 EventEmitter
  • 定义一个 middlewares 属性用来存放我们的中间件
  • 定义 use 方法,暂时把中间件存进去
  • 定义 listen 方法启动服务,调用 node 的 http 模块
  • callback 方法到 server 中
  • callback 中能拿到 node 提供的 resreq
  • 启动服务,打开网页可以看到 'hello world',一个简单的 koa 雏形就完成了

# 处理中间件

现在一个基本的服务已经启动了,但是我们的 middleware 还没有处理

const EventEmitter = require("events");
const http = require("http");

class Application extends EventEmitter {
  constructor() {
    super();
    this.middlewares = [];
  }
  use(middleware) {
    this.middlewares.push(middleware);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
  compose() {
    // console.log(this.middlewares);
    // 将所有的middlewares进行递归合并
    return async ctx => {
      function createNext(middleware, oldNext) {
        return async () => {
          await middleware(ctx, oldNext);
        };
      }
      let len = this.middlewares.length;
      let next = async () => {
        return Promise.resolve();
      };
      for (let i = len - 1; i >= 0; i--) {
        let currentMiddleware = this.middlewares[i];
        next = createNext(currentMiddleware, next);
      }
      await next();
    };
  }
  callback() {
    return (req, res) => {
      let fn = this.compose();
      const ctx = {};
      return fn(ctx).then(() => {
        res.end("hello world");
      });
    };
  }
}

module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

compose 方法就是用来合并中间件的,接下来分步骤分析一下这个方法

所有的 middwares

// 中间件1
async (ctx, next) => {
  console.log("入口1");
  await next();
  console.log("出口2");
};

// 中间件2
async (ctx, next) => {
  console.log("入口2");
  await next();
  console.log("出口1");
};
1
2
3
4
5
6
7
8
9
10
11
12
13

koa 中间件采用洋葱模型,所以我们最后想要的目标中间件应该是这样的

async (ctx, next) => {
  console.log("入口1");
  console.log("入口2");
  await Promise.resolve();
  console.log("出口1");
  console.log("出口2");
};
1
2
3
4
5
6
7

那么compose就要做几件事了

  1. 从 middlewares 末尾开始循环
  2. 取出了数组的最后一项
  3. 执行了两个重要的步骤
let next = async () => {
  return Promise.resolve();
};
next = createNext(currentMiddleware, next);
1
2
3
4
  1. 上面两个步骤执行完之后得到了新的 next
next = createNext(
  async (ctx, next) => {
    console.log("入口2");
    await next();
    console.log("出口1");
  },
  async () => {
    return Promise.resolve();
  }
);
1
2
3
4
5
6
7
8
9
10
  1. createNext 执行结束返回了什么
function createNext(middleware, oldNext) {
  return async () => {
    // await middleware(ctx, oldNext);
    // 变成了下面的东西
    await (async (ctx, next) => {
      console.log("入口2");
      await Promise.resolve();
      console.log("出口1");
    });
  };
}
1
2
3
4
5
6
7
8
9
10
11
  1. 再去继续执行遍历,此时的 next 就变成了上一步的返回值

中间件 1

async (ctx, next) => {
  console.log("入口1");
  // 把中间件1的 await next() 替换成了下面这一坨
  async () => {
    await (async (ctx, next) => {
      console.log("入口2");
      await Promise.resolve();
      console.log("出口1");
    });
  };
  console.log("出口2");
};
1
2
3
4
5
6
7
8
9
10
11
12

上面一个简单的 koa 就搞出来了,接下来还要处理一下 context、request、response 和容错

# 编写 request 和 response 文件

创建 request.js 和 response.js 文件

# request 文件

const url = require("url");
// 封装源生 request 操作
// 例如:增加quert方法,快速定位参数。headers 方法快速扩区headers字段
module.exports = {
  get query() {
    return url.parse(this.req.url, true).query;
  },
  get url() {
    return this.req.url;
  }
};
1
2
3
4
5
6
7
8
9
10
11

# response 文件

// 封装源生 response 操作
// 例如:body 方法,统一返回数据到客户端。socket 方法,快速获取 res 中的 socket 对象。
module.exports = {
  get body() {
    return this._body;
  },
  set body(data) {
    this._body = data;
  },
  get status() {
    return this.res.statusCode;
  },
  set status(code) {
    if (typeof code !== "number") {
      throw new Error("statusCode 只能是数字");
    }
    this.res.statusCode = code;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 编写 context 文件

context 文件主要负责代理 request 和 response

let proto = {};

function delegateSet(property, name) {
  proto.__defineSetter__(name, function(val) {
    this[property][name] = val;
  });
}

function delegateGet(property, name) {
  proto.__defineGetter__(name, function() {
    return this[property][name];
  });
}

// 定义需要代理的属性
let requestSet = [];
let requestGet = ["query", "url"];

let responseSet = ["body", "status"];
let responseGet = responseSet;

requestSet.forEach(item => {
  delegateSet("request", item);
});
requestGet.forEach(item => {
  delegateGet("request", item);
});

responseSet.forEach(item => {
  delegateSet("response", item);
});
responseGet.forEach(item => {
  delegateGet("response", item);
});

module.exports = proto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 合并及容错

上面步骤编写完了 context、request、response,接下来重新修改一下我们的 application 文件

修改 createContext 方法

class Application extends EventEmitter {
  createContext(req, res) {
    let ctx = Object.create(this.context);
    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;
  }
  callback() {
    return (req, res) => {
      let fn = this.compose();
      const ctx = this.createContext(req, res);
      return fn(ctx)
        .then(() => {
          res.end("hello world");
        })
        .catch(this.onerror(ctx));
    };
  }
}

module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

添加容错

onerror(ctx) {
        return err => {
            if (err.code === 'ENOENT') {
                ctx.status = 404;
            } else {
                ctx.status = 500;
            }
            let msg = err.message;
            ctx.res.end(msg);
            this.emit('error', err);
        };
    }
1
2
3
4
5
6
7
8
9
10
11
12

# 最终代码

application.js

const EventEmitter = require("events");
const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");

class Application extends EventEmitter {
  constructor() {
    super();
    this.middlewares = [];
    this.context = context;
    this.request = request;
    this.response = response;
  }
  use(middleware) {
    this.middlewares.push(middleware);
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
  createContext(req, res) {
    let ctx = Object.create(this.context);
    ctx.request = this.request;
    ctx.response = this.response;
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }
  onerror(ctx) {
    return err => {
      if (err.code === "ENOENT") {
        ctx.status = 404;
      } else {
        ctx.status = 500;
      }
      let msg = err.message;
      ctx.res.end(msg);
      this.emit("error", err);
    };
  }
  compose() {
    // console.log(this.middlewares);
    // 将所有的middlewares进行递归合并
    return async ctx => {
      function createNext(middleware, oldNext) {
        return async () => {
          await middleware(ctx, oldNext);
        };
      }
      let len = this.middlewares.length;
      let next = async () => {
        return Promise.resolve();
      };
      for (let i = len - 1; i >= 0; i--) {
        let currentMiddleware = this.middlewares[i];
        next = createNext(currentMiddleware, next);
      }
      await next();
    };
  }
  callback() {
    return (req, res) => {
      let fn = this.compose();
      const ctx = this.createContext(req, res);
      return fn(ctx)
        .then(() => {
          res.end("hello world");
        })
        .catch(this.onerror(ctx));
    };
  }
}

module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75