一步步去阅读koa源码,整体架构分析
阅读好的框架的源码有很多好处,从大神的视角去理解整个框架的设计思想。大到架构设计,小到可取的命名风格,还有设计模式、实现某类功能使用到的数据结构和算法等等。
使用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.js
、context.js
、requrest.js
和response.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.request
、this.response
、this.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
整个流程,还有很多细节都没有一一介绍到,比如、创建context
、request
、response
对象是怎么样子的呀?中间件是如何集中层层深入处理然后返回的呀?等等这些细节都会在下一篇会讲到(最近公司业务非常忙,不知道到猴年马月)。
写的不好的地方,让大家贱笑了。
然后最后安利一波博客,喜欢的小哥哥小姐姐可以star 哟