koa原理实现

534 阅读4分钟

Koa 原理实现

零、Koa 源码目录结构

.
├── History.md
├── LICENSE
├── Readme.md
├── dist
│   └── koa.mjs
├── lib
│   ├── application.js  # 最核心的模块
│   ├── context.js # 上下文对象
│   ├── request.js # Koa 自己实现的请求对象
│   └── response.js # Koa 自己实现的响应对象
└── package.json

一、基本结构

使用:

const Koa = require('./koa')

const app = new Koa()

app.listen(3000)

application.js

module.exports = class Application {
  listen(...args) {
    const server = http.createServer((req, res) => {
      res.end('My Koa')
    })
    return server.listen(...args)
  }
}

二、实现中间件功能

  • Koa 会把所有中间件组合成一个大的 Promise

  • 当这个 Promise 执行完毕之后,会采用当前的 ctx.body 进行结果响应

  • next 前面必须有 await 或者 return next,否则执行顺序达不到预期

  • 如果都是同步执行,加不加 await 都无所谓

  • 我不知道后续是否有异步逻辑,所以建议写的时候都加上 await next

收集中间件

使用方式:

const Koa = require('./koa')

const app = new Koa()

app.use((ctx, next) => {
  ctx.body = 'foo'
})

app.use((ctx, next) => {
  ctx.body = 'Koa'
})

app.listen(3000)

application.js

const http = require("http");

module.exports = class Application {
  constructor() {
    this.middleware = [];
  }

  use(fn) {
    if (typeof fn !== "function") {
      throw new TypeError("middleware must be a function!");
    }
    this.middleware.push(fn);
  }

  listen(...args) {
    const server = http.createServer((req, res) => {
      res.end("My Koa");
    });
    return server.listen(...args);
  }
};

const http = require("http");

class Application {
  constructor() {
    this.middleware = []; // 保存用户添加的中间件函数
  }

  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }

  use(fn) {
    this.middleware.push(fn);
  }

  // 异步递归遍历调用中间件处理函数
  compose(middleware) {
    return function () {
      const dispatch = (index) => {
        if (index >= middleware.length) return Promise.resolve();
          
        const fn = middleware[index];
        return Promise.resolve(
          // TODO: 上下文对象
          fn({}, () => dispatch(index + 1)) // 这是 next 函数
        );
      };

      // 返回第 1 个中间件处理函数
      return dispatch(0);
    };
  }

  callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) => {
      fnMiddleware()
        .then(() => {
          console.log("end");
          res.end("My Koa");
        })
        .catch((err) => {
          res.end(err.message);
        });
    };

    return handleRequest;
  }
}

module.exports = Application;

测试中间件执行流程

/**
 * - Koa 中间件功能
 *  + use 的时候收集起来
 *  + 请求进来的时候调用
 */

const Koa = require("./koa");

const app = new Koa();

const one = (ctx, next) => {
  console.log(">> one");
  next();
  console.log("<< one");
};

const two = (ctx, next) => {
  console.log(">> two");
  next();
  console.log("<< two");
};

const three = (ctx, next) => {
  console.log(">> three");
  next();
  console.log("<< three");
};

app.use(one);
app.use(two);
app.use(three);

// console.log(app.middleware)

app.listen(3000);

三、处理上下文对象

初始化上下文对象

context 上下文对象的使用方式:

/**
 * Koa Context
 */

const Koa = require("./koa");

const app = new Koa();

app.use(async (ctx, next) => {
  // Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。
  // 每个请求都将创建一个 Context,并在中间件中作为参数引用
  console.log(ctx); // Context 对象
  console.log(ctx.req.url);
  console.log(ctx.req.method);
  console.log(ctx.request.req.url);
  console.log(ctx.request.req.method);

  console.log(ctx.req); // Node 的 request 对象
  console.log(ctx.res); // Node 的 response 对象
  console.log(ctx.req.url);

  console.log(ctx.request); // Koa 中封装的请求对象
  console.log(ctx.request.header); // 获取请求头对象
  console.log(ctx.request.method); // 获取请求方法
  console.log(ctx.request.url); // 获取请求路径
  console.log(ctx.request.path); // 获取不包含查询字符串的请求路径
  console.log(ctx.request.query); // 获取请求路径中的查询字符串

  // Request 别名
  // 完整列表参见:https://koa.bootcss.com/#request-
  console.log(ctx.header);
  console.log(ctx.method);
  console.log(ctx.url);
  console.log(ctx.path);
  console.log(ctx.query);

  // Koa 中封装的响应对象
  console.log(ctx.response);
  ctx.response.status = 200;
  ctx.response.message = "Success";
  ctx.response.type = "plain";
  ctx.response.body = "Hello Koa";

  // Response 别名
  ctx.status = 200;
  ctx.message = "Success";
  ctx.type = "plain";

  ctx.body = "Hello Koa";
});

app.listen(3000);

context.js

const context = {
  
}

module.exports = context

request.js

const url = require("url");

const request = {
  get method() {
    return this.req.method;
  },

  get header() {
    return this.req.headers;
  },

  get url() {
    return this.req.url;
  },

  get path() {
    return url.parse(this.req.url).pathname;
  },

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

module.exports = request;

response.js

const response = {
  set status(value) {
    this.res.statusCode = value;
  },
};

module.exports = response;

application.js

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

class Application {
  constructor() {
    this.middleware = []; // 保存用户添加的中间件函数

    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }

  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }

  use(fn) {
    this.middleware.push(fn);
  }

  // 异步递归遍历调用中间件处理函数
  compose(middleware) {
    return function (context) {
      const dispatch = (index) => {
        if (index >= middleware.length) return Promise.resolve();
        const fn = middleware[index];
        return Promise.resolve(
          // TODO: 上下文对象
          fn(context, () => dispatch(index + 1)) // 这是 next 函数
        );
      };

      // 返回第 1 个中间件处理函数
      return dispatch(0);
    };
  }

  // 构造上下文对象
  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;
  }

  callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) => {
      // 每个请求都会创建一个独立的 Context 上下文对象,它们之间不会互相污染
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() => {
          res.end("My Koa");
        })
        .catch((err) => {
          res.end(err.message);
        });
    };

    return handleRequest;
  }
}

module.exports = Application;

处理上下文对象中的别名

context.js

const context = {
  // get method () {
  //   return this.request.method
  // },
  // get url () {
  //   return this.request.url
  // }
};

defineProperty("request", "method");
defineProperty("request", "url");

function defineProperty(target, name) {
  context.__defineGetter__(name, function () {
    return this[target][name];
  });
  // Object.defineProperty(context, name, {
  //   get () {
  //     return this[target][name]
  //   }
  // })
}

module.exports = context;

四、处理 ctx.body

保存 body 数据

response.js

const response = {
  set status(value) {
    this.res.statusCode = value;
  },

  _body: "", // 真正用来存数据的

  get body() {
    return this._body;
  },

  set body(value) {
    this._body = value;
  },
};

module.exports = response;

发送 body 数据

context.js

const context = {
  // get method () {
  //   return this.request.method
  // },
  // get url () {
  //   return this.request.url
  // }
};

defineProperty("request", "method");
defineProperty("request", "url");
defineProperty("response", "body");

function defineProperty(target, name) {
  // context.__defineGetter__(name, function () {
  //   return this[target][name]
  // })
  Object.defineProperty(context, name, {
    get() {
      return this[target][name];
    },

    set(value) {
      this[target][name] = value;
    },
  });
}

module.exports = context;

application.js

callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) => {
      // 每个请求都会创建一个独立的 Context 上下文对象,它们之间不会互相污染
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() => {
          res.end(context.body)
          // res.end('My Koa')
        })
        .catch((err) => {
          res.end(err.message);
        });
    };

    return handleRequest;
  }

更灵活的 body 数据

callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) => {
      // 每个请求都会创建一个独立的 Context 上下文对象,它们之间不会互相污染
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() => {
          respond(context);
          // res.end(context.body)
          // res.end('My Koa')
        })
        .catch((err) => {
          res.end(err.message);
        });
    };

    return handleRequest;
  }



function respond(ctx) {
  const body = ctx.body;
  const res = ctx.res;

  if (body === null) {
    res.statusCode = 204;
    return res.end();
  }

  if (typeof body === "string") return res.end(body);
  if (Buffer.isBuffer(body)) return res.end(body);
  if (body instanceof Stream) return body.pipe(ctx.res);
  if (typeof body === "number") return res.end(body + "");
  if (typeof body === "object") {
    const jsonStr = JSON.stringify(body);
    return res.end(jsonStr);
  }
}