Open loveky opened 8 years ago
最近在学习ES6 Generator特性时发现了koa这个基于Generator的Web框架,它可以让开发者以一种“同步的方式”编写包含各种异步请求的Web应用。下面是关于它的一段中文介绍:
由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
从介绍中可以看出这又是一个小而美的框架。到GitHub项目页面上看了一下发现源码里只有4个JS文件,总代码量只有1571行(以2015年12月26日最新稳定版1.1.2为准)。于是决定把代码clone下来学习一下,本篇会先从整个框架的入口文件lib/application.js说起。
lib/application.js
lib/application.js文件export出的是一个构造函数,用来创建一个koa应用。一个koa应用最常用的方法有2个:
listen(port)
http.createServer
use(middleware)
更多详细信息可以参考GitHub文档页面。
function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || 'development'; this.subdomainOffset = 2; this.middleware = []; this.proxy = false; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
以上就是Application构造函数的定义,很简洁,主要做了以下几件事情:
Application
instanceof
new
NODE_ENV
development
this.middleware
this.context
this.request
this.response
lib
this
prototype
context
request
response
lib/request.js
Object.setPrototypeOf(Application.prototype, Emitter.prototype);
在koa应用的原型链上追加一级Emitter.prototype,方便事件处理。
Emitter.prototype
app.listen = function(){ debug('listen'); var server = http.createServer(this.callback()); return server.listen.apply(server, arguments); };
listen方法会创建一个http.Server实例,并将所有参数转给这个http.Server实例的listen方法。这里需要注意的是,koa应用的listen方法是可以调用多次的。可以通过多以调用listen方法来创建多个事例监听在不同端口上。
listen
http.Server
app.inspect = app.toJSON = function(){ return only(this, [ 'subdomainOffset', 'proxy', 'env' ]); };
覆写koa应用的toJSON方法,并创建别名inspect。通过代码可以看出,在把koa应用以JSON格式输出时只会输出这个应用的subdomainOffset, proxy和env这三条信息。
toJSON
inspect
subdomainOffset
proxy
env
app.use = function(fn){ if (!this.experimental) { // es7 async functions are allowed assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function'); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; };
app.use方法用来注册一个中间件,默认传入的参数必须是一个Generator,否则报错。如果开启了experimental选项,则不再检查传入参数的类型,而是利用ES7中的async/await特性进行流程控制。
app.use
experimental
async/await
app.callback = function(){ var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware)); var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); return function(req, res){ res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror); } };
app.callback用来根据当前的middleware队列生成一个callback,该callback会在app.listen操作中创建http.Server时作为参数传递给新创建的Server实例,用来处理客户端请求。
app.callback
middleware
app.listen
第2至4行根据中间件队列生成一个普通函数fn,执行fn会返回一个Promise。如果中间件执行的过程中遇到错误,这个promise会被reject。在创建fn时依旧会判断experimental参数是否启用。如果该参数启用,则利用composition模块进行转换。否则使用co + koa-compose 转换。
fn
第7行判断当前应用是否注册了错误处理函数,如果没有则使用koa框架默认的默认值。
第9-16行则是实际处理请求的方法,接受一个request和一个response对象。首先koa会将node默认的request和response对象封装成一个koa的context对象。这个context对象就是我们中间件函数中的this,这样就可以很方便的访问与请求相关的信息。接着通过on-finished模块给当前请求注册一个callback,当处理该请求的过程中遇到错误时,该callback会根据错误信息进行一些状态设置,比如404,500状态码。最后就是调用fn方法开始处理请求。如果顺利执行,则会调用respond方法对response做最后处理,如果执行遇到错误,则执行context的onerror方法处理。
respond
onerror
app.createContext方法用于创建与一个请求对应的context对象。内容基本是一些属性的设置,此处不再详述。代码参考GitHub页面。 app.onerror为koa默认的错误处理函数。逻辑很简单,不再详述。代码参考GitHub页面。
app.createContext
app.onerror
最后的respond方法是一个helper方法,用来辅助设置一些response的信息。由于函数体较长,我们分段来看。
function respond() { // allow bypassing koa if (false === this.respond) return; var res = this.res; if (res.headersSent || !this.writable) return;
如果context对象的respond属性为false,则不执行该函数的逻辑。 接下来判断此响应的头部是否已经发出或是该响应是否不可写,如果任何一个条件为真,则跳过此函数逻辑。
false
var body = this.body; var code = this.status; // ignore body if (statuses.empty[code]) { // strip headers this.body = null; return res.end(); }
利用statuses判断此响应的状态码是否对应一个空响应体(例如204,304)。如果为真,则直接返回空响应。
if ('HEAD' == this.method) { if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body)); return res.end(); }
如果是HEAD请求并且对应的响应资源是一个JSON对象,则通过JSON.stringify计算响应体长度。
HEAD
JSON.stringify
// status body if (null == body) { this.type = 'text'; body = this.message || String(code); this.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); this.length = Buffer.byteLength(body); res.end(body); }
最后是针对不同类型的响应体进行处理,如Buffer, 字符串, Stream 和 JSON。
以上就是lib/applicaton.js的主要代码的注释了,其实只有几个方法而已,是不是很简单?
lib/applicaton.js
写的非常详细啊, 有空我也研究一下。。。
写的很赞,在自己读代码的时候看到这么详细的解释简直太幸福了
@panyifei 很高兴能帮到你
最近在学习ES6 Generator特性时发现了koa这个基于Generator的Web框架,它可以让开发者以一种“同步的方式”编写包含各种异步请求的Web应用。下面是关于它的一段中文介绍:
从介绍中可以看出这又是一个小而美的框架。到GitHub项目页面上看了一下发现源码里只有4个JS文件,总代码量只有1571行(以2015年12月26日最新稳定版1.1.2为准)。于是决定把代码clone下来学习一下,本篇会先从整个框架的入口文件
lib/application.js
说起。总体说明
lib/application.js
文件export出的是一个构造函数,用来创建一个koa应用。一个koa应用最常用的方法有2个:listen(port)
执行listen后会通过http.createServer
启动一个服务器并监听指定端口use(middleware)
注册一个中间件,一个koa应用可以注册多个中间件, 处理请求时会按照中间件注册的顺序执行这些中间件。更多详细信息可以参考GitHub文档页面。
代码注释
以上就是
Application
构造函数的定义,很简洁,主要做了以下几件事情:instanceof
判断来支持不带new
关键字的调用。NODE_ENV
读取,默认值为development
this.middleware
属性,默认为空数组,用来保存所有注册的中间件this.context
,this.request
,this.response
对象,分别继承自koa项目里lib
目录下对应文件export出来的对象。需要注意的是,这3个在this
上的属性仍然只是prototype
。每当一个请求到来时,koa会以这些对象为原型创建出与每个请求对应的context
,request
以及response
对象。这样处理的好处是lib/request.js
文件中定义的是koa框架级别的方法。this.request
对象上我们可以定义项目级别里每个request需要用到的方法,属性。在koa应用的原型链上追加一级
Emitter.prototype
,方便事件处理。listen
方法会创建一个http.Server实例,并将所有参数转给这个http.Server
实例的listen方法。这里需要注意的是,koa应用的listen
方法是可以调用多次的。可以通过多以调用listen
方法来创建多个事例监听在不同端口上。覆写koa应用的
toJSON
方法,并创建别名inspect
。通过代码可以看出,在把koa应用以JSON格式输出时只会输出这个应用的subdomainOffset
,proxy
和env
这三条信息。app.use
方法用来注册一个中间件,默认传入的参数必须是一个Generator,否则报错。如果开启了experimental
选项,则不再检查传入参数的类型,而是利用ES7中的async/await
特性进行流程控制。app.callback
用来根据当前的middleware
队列生成一个callback,该callback会在app.listen
操作中创建http.Server
时作为参数传递给新创建的Server实例,用来处理客户端请求。第2至4行根据中间件队列生成一个普通函数
fn
,执行fn
会返回一个Promise。如果中间件执行的过程中遇到错误,这个promise会被reject。在创建fn
时依旧会判断experimental
参数是否启用。如果该参数启用,则利用composition模块进行转换。否则使用co + koa-compose 转换。第7行判断当前应用是否注册了错误处理函数,如果没有则使用koa框架默认的默认值。
第9-16行则是实际处理请求的方法,接受一个request和一个response对象。首先koa会将node默认的request和response对象封装成一个koa的
context
对象。这个context
对象就是我们中间件函数中的this,这样就可以很方便的访问与请求相关的信息。接着通过on-finished模块给当前请求注册一个callback,当处理该请求的过程中遇到错误时,该callback会根据错误信息进行一些状态设置,比如404,500状态码。最后就是调用fn
方法开始处理请求。如果顺利执行,则会调用respond
方法对response做最后处理,如果执行遇到错误,则执行context
的onerror
方法处理。app.createContext
方法用于创建与一个请求对应的context
对象。内容基本是一些属性的设置,此处不再详述。代码参考GitHub页面。app.onerror
为koa默认的错误处理函数。逻辑很简单,不再详述。代码参考GitHub页面。最后的
respond
方法是一个helper方法,用来辅助设置一些response的信息。由于函数体较长,我们分段来看。如果
context
对象的respond
属性为false
,则不执行该函数的逻辑。 接下来判断此响应的头部是否已经发出或是该响应是否不可写,如果任何一个条件为真,则跳过此函数逻辑。利用statuses判断此响应的状态码是否对应一个空响应体(例如204,304)。如果为真,则直接返回空响应。
如果是
HEAD
请求并且对应的响应资源是一个JSON对象,则通过JSON.stringify
计算响应体长度。如果响应体为空,则设置一个默认的响应内容并设置响应体长度。
最后是针对不同类型的响应体进行处理,如Buffer, 字符串, Stream 和 JSON。
以上就是
lib/applicaton.js
的主要代码的注释了,其实只有几个方法而已,是不是很简单?