从0到1实现koa

280 阅读3分钟

Demo地址

github.com/zsjun/self_…

Init 开发环境

1, 初始化很简单,直接使用yarn init就可以。
2, 创建源码目录src

搭建基本的web服务

1, 这里很简单,就是使用把http.creatServe包装一层,代码很简单 1.1 src/app.js

const http = require("http");
class MyKoa {
  constructor(props) {
    this.midderWare = [];
  }

  use(fn) {
    this.midderWare.push(fn);
  }
  listen(...args) {
    const server = http.createServer((req, res) => {
      this.midderWare.forEach(function(fn) {
        return fn(req, res);
      });
    });
    server.listen(...args);
  }
}
module.exports = MyKoa;

1.2 src/index.js

const MyKoa = require("./app.js");
const app = new MyKoa();
app.use((req, res) => {
  console.log("11111111");
});
app.use((req, res) => {
  console.log("22222222");
  res.end("hello world");
});
// 监听端口号
app.listen(3011, err => {
  console.log(err);
  if (!err) {
    console.log("server is starting");
  } else {
    console.log(err);
  }
});

2, vscode中添加lanch.json, 方便使用vscode进行调试。

{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "启动程序",
      "runtimeExecutable": "/.nvm/versions/node/v10.15.3/bin/node",
      "program": "/work/self_koa/src/index.js"
    }
  ]
}

封装原生的req,res和context

koa里边的req和res都不是原生的req和res,它是在原生的req和res上,为了使用方便,而进行重新进行封装了一层,让用户用起来更加方便。

封装req

其实封装很简单,顾名思义就是把req包装一下,让它有更多的特性,更加的方便使用。
1, 新建src/request.js

let url = require("url");

module.exports = {
  get query() {
    return url.parse(this.req.url, true).query;
  }
};

代码很简单,没什么好说的。
2, 测试requeset.js
上面的requeset.js, 可以看到它是使用了this.req.url, 所以需要改变一下this,让this对象能够有req对象,不然在request里边没法使用。 使用this.request.req = req来达到此目的。

const http = require("http");
const request = require("./request.js");
class MyKoa {
  constructor(props) {
    this.midderWare = [];
    this.request = request;
  }
  use(fn) {
    this.midderWare.push(fn);
  }
  listen(...args) {
    const server = http.createServer((req, res) => {
      this.request.req = req;
      this.midderWare.forEach(fn => {
        return fn(this.request.query, res);
      });
    });
    server.listen(...args);
  }
}
module.exports = MyKoa;

封装res

res的封装和封装req基本上差不多,可以根据自己的添加需要的方法和属性,
1, 新建src/response.js

module.exports = {
  get status() {
    return 300;
  },

  /**
   * 设置返回给客户端的stausCode
   *
   * @param {number} statusCode 状态码
   */
  set status(statusCode) {
    if (typeof statusCode !== "number") {
      throw new Error("statusCode must be a number!");
    }
    this.res.statusCode = statusCode;
  }
};

2, 测试response.js
修改app.js,和封装req一样,也需要把res传到对象里边,也就是用this.response.res=res

const http = require("http");
const request = require("./request.js");
const response = require("./response.js");
class MyKoa {
  constructor(props) {
    this.midderWare = [];
    this.request = request;
    this.response = response;
  }
  use(fn) {
    this.midderWare.push(fn);
  }
  listen(...args) {
    const server = http.createServer((req, res) => {
      this.request.req = req;
      this.response.res = res;
      this.midderWare.forEach(fn => {
        return fn(this.request.query, this.response);
      });
    });
    server.listen(...args);
  }
}
module.exports = MyKoa;

修改src/index.js

const MyKoa = require("./app.js");

const app = new MyKoa();
app.use((req, res) => {
  console.log("11111111", res.status);
});
app.use((req, res) => {
  console.log("22222222", req);
  res.res.end("hello world");
});
// 监听端口号
app.listen(3011, err => {
  console.log(err);
  if (!err) {
    console.log("server is starting");
  } else {
    console.log(err);
  }
});

测试结果

至于封装context,原理基本是一样的,没有什么可写的。

添加中间件

中间件其实也很简单,和redux的中间件有点类似,不过redux的中间件是通过来重写store.dispatch来实现,而koa的中间件也是通过控制中间件的顺序来实现,不过koa是通过重写next来实现的。 假设有三个中间件分别如下

async (req, res, next) => {
  console.log("1");
  await next(); // 调用下一个middleware
  console.log("5");
}

async (req, res, next) => {
  console.log("2");
  await next(); // 调用下一个middleware
  console.log("4");
}

async (req, res, next) => {
  console.log("3");
}

控制中间的顺序compose函数

代码很简单,就是不断的重写next,基本上和redux的思想差不多

compose() {
    return async (req, res) => {
      let next = async () => {
        return Promise.resolve();
      };
      const creatNext = (current, next) => {
        return async () => {
          await current(req, res, next);
        };
      };
      for (let i = this.midderWare.length - 1; i >= 0; i--) {
        next = creatNext(this.midderWare[i], next);
      }
      await next();
    };
  }

1, 修改src/app.js

const http = require("http");
const request = require("./request.js");
const response = require("./response.js");
class MyKoa {
  constructor(props) {
    this.midderWare = [];
    this.request = request;
    this.response = response;
  }
  use(fn) {
    this.midderWare.push(fn);
  }
  compose() {
    return async (req, res) => {
      let next = async () => {
        return Promise.resolve();
      };
      const creatNext = (current, next) => {
        return async () => {
          await current(req, res, next);
        };
      };
      for (let i = this.midderWare.length - 1; i >= 0; i--) {
        next = creatNext(this.midderWare[i], next);
      }
      await next();
    };
  }
  listen(...args) {
    const server = http.createServer((req, res) => {
      this.request.req = req;
      this.response.res = res;
      let fn = this.compose();
      return fn(req, res);
    });
    server.listen(...args);
  }
}
module.exports = MyKoa;


2, 修改src/index.js
const MyKoa = require("./app.js");
const app = new MyKoa();
app.use(async (req, res, next) => {
  console.log("1");
  await next(); // 调用下一个middleware
  console.log("5");
});
app.use(async (req, res, next) => {
  console.log("2");
  await next(); // 调用下一个middleware
  console.log("4");
});
app.use(async (req, res, next) => {
  console.log("3");
  // res.end("hello");
  // res.res.end("hello world");
});
// 监听端口号
app.listen(3011, err => {
  if (!err) {
    console.log("server is starting");
  } else {
    console.log(err);
  }
});