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对象进行一番解析,敬请期待。

Koa2 源码解析(1)的更多相关文章

  1. koa2源码解读及实现一个简单的koa2框架

    阅读目录 一:封装node http server. 创建koa类构造函数. 二:构造request.response.及 context 对象. 三:中间件机制的实现. 四:错误捕获和错误处理. k ...

  2. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  5. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  6. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  7. Spring IoC源码解析——Bean的创建和初始化

    Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...

  8. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  9. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

随机推荐

  1. Canvas入门(1):绘制矩形、圆、直线、曲线等基本图形

    来源:http://www.ido321.com/968.html 一.Canvas的基础知识 Canvas是HTML 5中新增的元素,专门用于绘制图形.canvas元素就相当于一块“画布”,一块无色 ...

  2. 运行时环境(The Runtime Environment)

    App Engine应用响应网络请求.当一个客户端(典型的是用户的Web浏览器)使用HTTP请求(比如获取在URL上的网页)连接上应用的时候,网络请求就开始了.当App Engine接收到请求时,它会 ...

  3. 有趣的Node爬虫,数据导出成Excel

    最近一直没更新了诶,因为学习Backbone好头痛,别问我为什么不继续AngularJs~因为2.0要出来了啊,妈蛋!好,言归正传,最近帮我的好基友扒数据,他说要一些股票债券的数据.我一听,那不就是要 ...

  4. 从 ALAsset 中取出属性

    #pragma mark - 从相册数组中取出所有的 图片数据 -(NSMutableArray *)getImageFromAlbumArray:(NSArray *)albumArr { NSMu ...

  5. 高扩展的基于NIO的服务器架构

    当你考虑写一个扩展性良好的基于Java的服务器时,相信你会毫不犹豫地使用Java的NIO包.为了确保你的服务器能够健壮.稳定地运行,你可能会花大量的时间阅读博客和教程来了解线程同步的NIO selec ...

  6. Bootstrap栅格系统(布局)

    栅格系统(布局) Bootstrap内置了一套响应式.移动设备优先的流式栅格系统,随着屏幕设备或视口(viewport)尺寸的增加,系统会自动分为最多12列. 我在这里是把Bootstrap中的栅格系 ...

  7. 基于EF的数据外键关联查询

    现在很多ORM不自带外键关联的实体查询,比如我查询用户,用时将关联的角色信息查询出来,那么就要进行2次查询,很麻烦.而我现在要做的就是基于EF的外键关联查询.很方便的. 首先,创建基础查询的BaseS ...

  8. IIS应用程序池性能分析

    #查看应用程序池和w3wp.exe进程的对应关系iisapp -a C:\windows\system32\inetsrv\appcmd.exe list wp 查看任务管理器: 在性能计数器中找到对 ...

  9. VMM服务模板(虚机、APP)部署排错

    I won't focus this blog on how to create a service template but more on how you can track the change ...

  10. Parallax Mapping Shader 凸凹感【转】

    原文 http://www.azure.com.cn/default.asp?cat=11&page=2 Parallax Mapping 就是通过高度图中的高度,对纹理坐标进行偏移,来视觉上 ...