Koa2 源码解析(1)
2016-12-09 10:42
357 查看
Koa2 源码解析
其实本来不想写这个系列文章的,因为Koa本身很精简,一共就4个文件,千十来行代码。但是因为想写 egg[1] 的源码解析,而egg是基于Koa2的,所以就先写个Koa2的吧,用作承上启下。
[1] egg 是阿里巴巴团队开源的企业级web开发框架
面向读者
我们假定读者具备javascript基础知识,简单了解promise、generator和async。入口
我们以 koajs中文官网 的例子作为入口。const Koa = require('koa'); const app = new Koa(); // response app.use(ctx => { ctx.body = 'Hello Koa'; }); app.listen(3000);
这样就启动起来了一个Koa2网站,可以看到只做了3件事: Koa的构造函数、app实例的use函数、app实例的listen函数。
查看Koa源码的package.json文件得知,默认入口是 application.js文件,也就是上面代码的Koa,那么让我们来看看里面是什么?
Application.js
module.exports = class Application extends Emitter {
我们可以看到Application是继承自 Event 模块的事件监听器,并且 Koa2 已经使用了 ES6 的 class 语法。
构造函数
constructor() { super(); this.proxy = false; this.middleware = []; this.subdomainOffset = 2; this.env = process.env.NODE_ENV || 'development'; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
下面来看看构造函数,也没什么稀奇的。
调用父类的构造函数,然后4个属性的初始化,我们先不管它们都是干什么的。
接下来是创建3个对象context、request和response,其实这就是koa2的核心了,构造出的context表示本次请求的上下文,request和response这个大家都知道。
use函数
use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); 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/tree/v2.x#old-signature-middleware-v1x---deprecated'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; }
use函数更简单:判断是不是function,判断是不是generator,如果是generator那么转换一下,将fn放入middleware数组。
listen函数
listen() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
listen看似也没什么,其实不然,获取createServer的callback函数是个核心的东西,我们来看下
callback() { const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); return (req, res) => { res.statusCode = 404; const ctx = this.createContext(req, res); const onerror = err => ctx.onerror(err); onFinished(res, onerror); fn(ctx).then(() => respond(ctx)).catch(onerror); }; }
首先,把所有middleware进行了组合,使用了koa-compose,我们也不用去管他的内部实现,简单来说就是返回了一个promise数组的递归调用。
然后,我们看看这个匿名函数,把http code默认设置为404,接着利用createContext函数把node返回的req和res进行了组合创建出context,可以看看createContext函数
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; }
这里面都是一堆的组合和赋值,例如把req挂到context下面啦、把request挂到reponse下面啦、获取下accept啦、ip啦。
回头来看看创建完context又干了些什么
const onerror = err => ctx.onerror(err); onFinished(res, onerror); fn(ctx).then(() => respond(ctx)).catch(onerror);
前两行没什么,给res注册了一个onerror事件,然后第三行就是具体所有middleware的执行了,所有middleware都执行完后,调用respond(ctx)函数
function respond(ctx) { // allow bypassing koa if (false === ctx.respond) return; const res = ctx.res; if (!ctx.writable) return; let body = ctx.body; const code = ctx.status; // ignore body if (statuses.empty[code]) { // strip headers ctx.body = null; return res.end(); } if ('HEAD' == ctx.method) { if (!res.headersSent && isJSON(body)) { ctx.length = Buffer.byteLength(JSON.stringify(body)); } return res.end(); } // status body if (null == body) { body = ctx.message || String(code); if (!res.headersSent) { ctx.type = 'text'; ctx.length = Buffer.byteLength(body); } return res.end(body); } // responses if (Buffer.isBuffer(body)) return res.end(body); if ('string' == typeof body) return res.end(body); if (body instanceof Stream) return body.pipe(res); // body: json body = JSON.stringify(body); if (!res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }
这个respond函数里面也不过是一些收尾工作,例如判断http code为空如何输出啦,http method是head如何输出啦,body返回是流或json时如何输出啦。
完事,你看koa是不是很简单,只不过是把res和req组合成了context,并提供了一些便利的函数,以及最重要的把middleware promise化了,写异步更爽了,加上es2017的async/await语法,跟C#也很像了。
接下来我们还会对context、request和response对象进行一番解析,敬请期待。
相关文章推荐
- koa-connect源码解析
- 网页病毒源码解析
- Jboss源码解析
- DotNet版的ExtJS单用户Blog系统源码解析
- 驱动程序源码解析(转)
- Linux Buddy系统算法源码解析
- chrome源码解析系列
- DotNet版的ExtJS单用户Blog系统源码解析
- 决定陆续发表一些EXT源码解析的文章
- EXT源码解析:EXT.js(二)
- jboss源码解析(zz)
- 解析来访IP获取163天气显示当地天气预报的实现(源码)
- [绝冬城]Sniff C++实现源码解析
- Ext源码分析:解析Ext的命名空间,Ext.namespace
- 表达式解析的(C#)源码
- 窗体界面控件源码解析
- GoF 23种设模式解析附C++实现源码(k_eckel转自微软高校博客K_eckel's mindview)
- nhibernate源码八: 解析HQL
- .Net版的ExtJS单用户Blog系统源码解析
- 棍子节快乐~ 顺便转一篇weka源码分析解析~【转自weka中文网】