最近空闲时间读了一下 Koa2 的源码;在阅读Koa2(version 2.2.0)的源码的过程中,我的感受是代码简洁、思路清晰(不得不佩服大神的水平)。
下面是我读完之后的一些感受。
Koa 是一个轻量级的、极富表现力的 http 框架。
一个web request会通过 Koa 的中间件栈,来动态完成 response 的处理。
Koa2 采用了 async 和 await 的语法来增强中间件的表现力。
Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库。
Koa源码非常精简,只有四个文件:
- // application.js
- module.exports = class Application extends Emitter {
- constructor() {
- super();
- this.proxy = false; // 是否信任 proxy header 参数,默认为 false
- this.middleware = []; //保存通过app.use(middleware)注册的中间件
- this.subdomainOffset = 2; // 子域默认偏移量,默认为 2
- this.env = process.env.NODE_ENV || 'development'; // 环境参数,默认为 NODE_ENV 或 ‘development’
- this.context = Object.create(context); //context模块,通过context.js创建
- this.request = Object.create(request); //request模块,通过request.js创建
- this.response = Object.create(response); //response模块,通过response.js创建
- }
- // ...
- }
application.js 是 koa 的入口主要文件,暴露应用的 class, 这个 class 继承自 EventEmitter ,这里可以看出跟 koa1.x 的不同,koa1.x 是用的是构造函数的方式,koa2 大量使用 es6 的语法。调用的时候就跟 koa1.x 有区别
- var koa = require('koa');
- // koa 1.x
- var app = koa();
- // koa 2.x
- // 使用class必须使用new来调用
- var app = new koa();
application.js除了上面的的构造函数外,还暴露了一些公用的api,比如两个常见的,一个是
,一个是
- listen
。
- use
- // application.js
- 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/blob/master/docs/migration.md');
- fn = convert(fn);
- }
- debug('use %s', fn._name || fn.name || '-');
- this.middleware.push(fn);
- return this;
- }
函数做的事很简单:注册一个中间件
- use
,其实就是将
- fn
放入
- fn
数组。
- middleware
- // application.js
- listen(...args) {
- debug('listen');
- const server = http.createServer(this.callback());
- return server.listen(...args);
- }
方法首先会通过
- listen
方法来返回一个函数作为
- this.callback
的回调函数,然后进行监听。我们已经知道,
- http.createServer
的回调函数接收两个参数:
- http.createServer
和
- req
,下面来看
- res
的实现:
- this.callback
- // application.js
- callback() {
- const fn = compose(this.middleware);
- if (!this.listeners('error').length) this.on('error', this.onerror);
- const handleRequest = (req, res) = >{
- res.statusCode = 404;
- const ctx = this.createContext(req, res);
- const onerror = err = >ctx.onerror(err);
- const handleResponse = () = >respond(ctx);
- onFinished(res, onerror);
- return fn(ctx).then(handleResponse).
- catch(onerror);
- };
- return handleRequest;
- }
首先,
方法把所有
- callback
进行了组合,使用了
- middleware
,我们来看一下
- koa-compose
的代码:
- koa-compose
- // koa-compose
- function compose (middleware) {
- // 传入的middleware必须是一个数组
- if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
- // 传入的middleware的每一个元素都必须是函数
- for (const fn of middleware) {
- if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
- }
- return function (context, next) {
- let index = -1
- return dispatch(0)
- function dispatch (i) {
- if (i <= index) return Promise.reject(new Error('next() called multiple times'))
- index = i
- let fn = middleware[i]
- //下面两行代码是处理最后一个中间件还有next的情况的,其实就是直接resolve出来
- if (i === middleware.length) fn = next
- if (!fn) return Promise.resolve()
- try {
- // 这里就是传入next执行中间件代码了
- return Promise.resolve(fn(context, function next () {
- return dispatch(i + 1)
- }))
- } catch (err) {
- return Promise.reject(err)
- }
- }
- }
- }
可以看到
基本就是个
- koa-compose
函数的递归调用。其中最重要的就是下面这段代码:
- dispatch
- return Promise.resolve(fn(context, function next () {
- return dispatch(i + 1)
- }))
这段代码等价于:
- fn(context,
- function next() {
- return dispatch(i + 1)
- }) return Promise.resolve()
这里
的第二个参数(也就是next)是动态传递进去的信使,它会调取
- middlewareFunction
执行下一个的
- dispatch(index)
。最后会返回一个
- middleware
(已完成)状态的Promise对象。这个对象的作用我们稍后再说。
- Resolved
我们先暂时回到
方法里面,前面说了它先对
- callback
进行了组合,生成了一个函数
- middleware
。
- fn
然后,
方法返回
- callback
所需要的回调函数
- http.createServer
。
- handleRequest
函数,先把http code默认设置为404,接着利用
- handleRequest
函数把node返回的req和res进行了封装创建出
- createContext
,
- context
然后通过
监听
- onFinished(res, onerror)
,当请求结束时执行回调。这里传入的回调是
- http response
,即当错误发生时才执行。
- context.onerror(err)
最后返回
的执行结果,这里的
- fn(ctx).then(handleResponse).catch(onerror)
函数就是就是组合所有
- fn
后生成的函数,调用它执行所有
- middleware
后会返回前面提到的
- middleware
(已完成)状态的Promise对象,之后执行响应处理函数
- Resolved
(
- respond(ctx)
函数里面也主要是一些收尾工作,例如判断http code为空如何输出啦,http method是head如何输出啦,body返回是流或json时如何输出;代码就不贴了,感兴趣的小伙伴自己可以去看一下),当抛出异常时同样使用
- respond
处理。
- context.onerror(err)
我们可以看看
函数
- createContext
- // application.js
- 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;
- }
创建
- createContext
的时候,还会同时创建request和response,通过下图可以比较直观地看到所有这些对象之间的关系。
- context
图中:
通过上面的分析,我们已经可以大概得知Koa处理请求的过程:当请求到来的时候,会通过 req 和 res 来创建一个 context (ctx) ,然后执行中间件。
content.js 主要的功能提供了对
和
- request
对象的方法与属性便捷访问能力。
- response
其中使用了 node-delegates (有兴趣的可以看一下 源码 ),将
与
- context.request
上的方法与属性代理到
- context.response
上。
- context
在源码中,我们可以看到:
- // context.js
- delegate(proto, 'response')
- .method('attachment')
- // ...
- .access('status')
- // ...
- .getter('writable');
- delegate(proto, 'request')
- .method('acceptsLanguages')
- // ...
- .access('querystring')
- // ...
- .getter('ip');
request.js 封装了请求相关的属性以及方法。通过 application.js 中的
方法,代理对应的 request 对象。
- createContext
- const request = context.request = Object.create(this.request);
- // ...
- context.req = request.req = response.req = req;
- // ...
- request.response = response;
为原生的请求对象,在 request.js 中属性的获取都是通过
- request.req
来获取的(即
- ths.req
)。
- request.req
response.js 封装了响应相关的属性以及方法。与 request 相同,通过
方法代理对应的 response 对象。
- createContext
- const response = context.response = Object.create(this.response);
- // ...
- context.res = request.res = response.res = res;
- // ...
- response.request = request;
关于Koa2的源码就先分析到这,希望对大家有所帮助。
如有不同的看法,欢迎交流!
来源: http://www.tuicool.com/articles/3U3qyuM