资讯专栏INFORMATION COLUMN

一步步去阅读koa源码,整体架构分析

haoguo / 2485人阅读

摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。

阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。
使用koa

其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个API是怎么用,哪里有坑,哪里设计的精妙。

下面我们就简单用一下koa这个框架,如下代码

const Koa = require("koa")
const app = new Koa()
app.use(async (ctx, next) => {
  ctx.body = "Hello World"
})
app.listen(4002)

运行结果

瓦特??这个服务会涉及到从请求到响应返回数据,就这几行代码?? 是的,你没有看错,就是单单这几行代码就可以搭建了一个服务器。

下面我们看看一探究竟。

阅读源码

去到node_modules文件夹下找到koa模块,先喵几眼README.md文件,里面介绍了koa的一些安装、用法、插件等等,这里我们跳过,然后转到package.json如下图

看到package.json里面的"main": "lib/application.js"没错,这就是我们的入口,在lib文件夹下面,我们看到里面有application.jscontext.jsrequrest.jsresponse.js。下面经过我修改简化去掉注释application.js就只有68行代码。阅读起来可以说是非常简单了。如下图:

第一步是我们引入各种主要依赖

// 引入有很多 我只挑我阅读主要框架的代码模块

const response = require("./response"); // 处理response对象
const compose = require("koa-compose"); // 合并处理中间件函数
const context = require("./context"); // 整合处理context对象
const request = require("./request"); // 整合处理request对象
const http = require("http"); // node的 http原生模块

以上就是我们的主要依赖

Application的对象中,有constructor函数,这个主要是初始化Application对象,生成context对象、request对象、response对象

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    super(); // 继承Emitter
    this.middleware = []; // 初始化middleware为空数组
    this.context = Object.create(context); // 生成context对象
    this.request = Object.create(request); // 生成request对象
    this.response = Object.create(response); // 生成response对象
  }
}

阅读源码,我们先不要去扣细节,比如说Object.create(context)生产的对象是什么?this.request对象下面又有什么东西???,我们现在主要知道的是、this.context是能获取或者设置请求和响应的信息的一个对象,。this.request是请求的对象、里面可以设置或者获取请求信息的一个对象、this.response是响应请求对象、里面可以设置或者获取响应参数和值的一个对象。大概先了解就可以了。继续往下看。

在上面运用的时候,用到了app.use(fn)app.listen(4002) 我们看看,源码里面试这样子的

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    ...
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  use(fn) {
    this.middleware.push(fn);
    return this;
  }

上面的代码很简单 use函数就是把传入的fn 推入到this.middleware的数组中,然后返回this,方便链式调用。

然后在listen里面用node原生的http模块创建一个server,在这里顺便说一下,原生 http创建一个服务是这样子滴

const http = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("okay");
});
http.listen(8000)

继续看代码 ,在创建服务的时候,参数里面调用了一个this.callback()函数,下面我们看看这个函数究竟是怎么样子的。

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    ...
  }
  listen(...args) {
    ...
  }
  use(fn) {
    ...
  }

  callback() {
    const fn = compose(this.middleware); // 集中处理中间件数组
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res); // 整合req、res、context、request、response
      return this.handleRequest(ctx, fn); // 返回handleRequest
    };
    return handleRequest;
  }
    
  handleRequest(ctx, fnMiddleware) {
    const handleResponse = () => respond(ctx); // 最终响应函数
    return fnMiddleware(ctx).then(handleResponse) // 处理完中间件,然后传到下一响应函数
  }
  // 创建整合新的 context.
  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.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || "";
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
  }
};

上面我们可以看出在callback函数里面有一个const fn = compose(this.middleware); 这个函数就是把this.middleware数组传进去,然后集中处理中间件,然后会返回处理完中间件的fn。

继续下一行

const handleRequest = (req, res) => {
  const ctx = this.createContext(req, res);
  return this.handleRequest(ctx, fn);
};

继续进入到handleRequest函数里面的const ctx = this.createContext(req, res);这个把原生的http的请求对象req响应对象res作为参数传进去,然后在createContext函数(看上面最大那坨代码)在里面,把this.requestthis.responsethis.context请求对象req响应对象res都整,做各种整合、处理得到新的context对象返回出去。

也就是强大的ctx,得到ctx之后,下一行返回return this.handleRequest(ctx, fn);

this.handleRequest(ctx, fn)代码如下

handleRequest(ctx, fnMiddleware) {
    const handleResponse = () => respond(ctx);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

这个函数 就是处理完中间件处理之后的返回的函数把ctx传下去,最后流通到respond(ctx);这个函数,

那么我们看看这个函数被我简化后是怎么样子的,如下

// 一些容错判断或者提示我全部删了
function respond(ctx) {
  const res = ctx.res;
  let body = ctx.body;
  res.end(body);
}

通过ctx拿到响应对象,和响应值、通过end方法会通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成。看上面原生的http的服务方法。

最后附上一个流程图

这个只是介绍application整个流程,还有很多细节都没有一一介绍到,比如、创建contextrequestresponse对象是怎么样子的呀?中间件是如何集中层层深入处理然后返回的呀?等等这些细节都会在下一篇会讲到(最近公司业务非常忙,不知道到猴年马月)。

写的不好的地方,让大家贱笑了。

然后最后安利一波博客,喜欢的小哥哥小姐姐可以star 哟

websit: https://github.com/naihe138/naice-blog

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95040.html

相关文章

  • 步步阅读koa源码整体架构分析

    摘要:阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。使用其实某个框架阅读源码的时候,首先我们要会去用这个框架,因为用了我们才知道,某个是怎么用,哪里有坑,哪里设计的精妙。 阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。 使用koa 其实某个框架阅读源码的时候,首...

    chaos_G 评论0 收藏0
  • 步步阅读koa源码,中间件执行原理

    摘要:设置了一个出事索引值调用函数,开始时候传进去声明函数,把传进来的赋值给拿出第个中间件函数,赋值给判断如果等于的长度就把赋值给如果是假的返回一个同时执行,也就是中间件,把函数传递到里面递归调用自己上面的代码是这个部分的精华。 koa的中间件执行的流程控制,代码的是非常精妙的。由下面的一张洋葱模型的图来形容,记住这张图。 showImg(https://segmentfault.com/i...

    WelliJhon 评论0 收藏0
  • B站Up主-山地人-这位老哥2019年的前端自学计划进展如何?——讲个B站Up主自学前端85天的故

    摘要:前言自从上次在掘金发布年山地人的前端完整自学计划讲一个站主山地人的天前端自学故事以来,一眨眼山地人老哥在站做主已经有天了。所以这个体系里的一些框架包括也是山地人年自学计划的一部分。月底,山地人老哥开启了的两个专题。 前言 自从上次在掘金发布【2019年山地人的前端完整自学计划——讲一个B站UP主山地人的40天前端自学故事】 以来,一眨眼山地人老哥在B站做Up主已经有85天了。 时隔一个...

    cocopeak 评论0 收藏0
  • 教你从写个迷你koa-router到阅读koa-router源码

    摘要:本打算教一步步实现,因为要解释的太多了,所以先简化成版本,从实现部分功能到阅读源码,希望能让你好理解一些。 本打算教一步步实现koa-router,因为要解释的太多了,所以先简化成mini版本,从实现部分功能到阅读源码,希望能让你好理解一些。希望你之前有读过koa源码,没有的话,给你链接 最核心需求-路由匹配 router最重要的就是路由匹配,我们就从最核心的入手 router.get...

    yzzz 评论0 收藏0

发表评论

0条评论

haoguo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<