Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

koa为什么能实现洋葱模型 #34

Open
xixigiggling opened this issue Sep 27, 2019 · 0 comments
Open

koa为什么能实现洋葱模型 #34

xixigiggling opened this issue Sep 27, 2019 · 0 comments
Labels
JS javascript 填坑中... 正在完成

Comments

@xixigiggling
Copy link
Owner

xixigiggling commented Sep 27, 2019

经历了一波感觉暗无天日的秋招以后,总算能稍微松口气了,先把自己一直想写的博客,记录一下

源码的入口

首先,可以看到的是koa源码中就只有lib目录里面有四个文件:

  • application.js
  • context.js
  • request.js
  • response.js

按我们的使用方法:

const Koa = require('koa');
const app = new Koa();

首先第一个疑问就来了:由上面的代码可知require('koa')得到了koa的构造函数,那么,koa的构造函数是通过哪个文件暴露出来的呢?

换言之,也就是我们阅读koa源码从哪里开始呢?

于是得知道require('koa')发生了什么,已知require是node的模块机制

require()方法接受一个标识符作为参数。在Node实现中,正是基于这样一个标识符进行模块查找的。模块标识符在Node中主要分为以下几类:

  • 核心模块,如http,fs等
  • .或..开始的相对路径文件模块
  • 以/开始的绝对路径文件模块
  • 非路径形式的文件模块,如自定义的connect模块

于是我们引入的便属于自定义模块:

自定义模块的查找:

  1. 当前文件目录下的node_modules目录
  2. 父目录下的node_modules目录
  3. 父目录的父目录下的node_modules目录
  4. 沿着路径向上逐级递归,直到根目录的node_modules目录

于是进一步揭晓:先到我们的当前文件目录下的node_modules目录查找,找到了查找的地方,然后呢?

但在文件的定位过程中,还有一些细节需要注意,这主要包括文件拓展名的分析、目录和包的处理

于是首先,koa这个标识符不包含文件拓展名,Node会按.js,.json,.node的次序补足拓展名依次尝试,但是我们可以看自己的node_modules目录里面koa是一个目录...

此时Node会将目录当作一个包来处理

那么是一个包就会有package.json,于是:

Node会在当前目录下查找package.json,通过JSON.parse()解析出包描述对象,从而取出main属性指定的文件名进行定位
如果文件名缺少扩展名,则进入扩展名分析环节
如果文件名错误,或者没有package.json,则Node会把index当做默认文件名,然后依次查找index.js,index.json,index.node
...

注意这里如果package.json没有main属性,应该也是直接使用index当做默认文件名,例如koa-compose的包:

  • History.md
  • index.js(直接在index中module.exports = compose了)
  • package.json
  • Readme.md(没有main属性)

回到koa源码中,正好node_modules的koa目录下的package.json中有main属性:

"main": "lib/application.js"

然后再去application.js中去查找:module.exports = class Application extends Emitter { // code }

答案揭晓:通过require('koa')得到的Koa构造函数,其实是lib/application.js中暴露出来的class Application

于是,终于走了小小的一步...

参考:《深入浅出Node.js》

开始探索

我们知道了const Koa = require('koa');,中的Koa代表的是,class Application,接下来回到我们要探索的问题:为什么koa会表现成一个洋葱模型?

如下:

const Koa = require("koa");
const app = new Koa();

app.use(async (ctx, next) => {
    console.log(1);
    await next();
    console.log(2);
});

app.use(async (ctx, next) => {
    console.log(3);
    await next();
    console.log(4);
});

app.use(ctx => {
    ctx.body = "Hello Koa";
});

app.listen(3000);
console.log('server start: 3000');

// output:
// 1
// 3
// 4
// 2

所以我们开始从application.js中寻找答案,首先示例代码中使用了use,我们可以从application.js中去找到use方法:

 use(fn) {
    // 边界判断
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    // 如注释所示,为了转化generator
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
	// 这里是核心
    this.middleware.push(fn);
    return this;
  }

由于构造函数中,this.middleware = [];,得知应该是把fn放入数组中存储起来

于是这里use函数就结束了,然后就到了app.listen(3000),接着看listen方法

listen(...args) {
	debug('listen');
    // 使用了http模块的createServer与Appalication的callback方法
	const server = http.createServer(this.callback());
	return server.listen(...args);
}

然后我们再看callback方法:

callback() {
    // 把之前存储函数middleware,传给了compose函数?
    const fn = compose(this.middleware);

    if (!this.listenerCount('error')) this.on('error', this.onerror);

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

    return handleRequest;
  }
// todo
@xixigiggling xixigiggling added JS javascript 填坑中... 正在完成 labels Sep 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS javascript 填坑中... 正在完成
Projects
None yet
Development

No branches or pull requests

1 participant