最近项目要使用koa,因此提早学习一下,顺便看了koa框架的源码.html
注:源码是koa2.xnode
koa的源码很简洁,关键代码只有4个文件,固然还包括一些依赖npm包git
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { await next(); ctx.type = 'text/html'; ctx.body = '<h1>Hello, koa2!</h2>'; }); app.listen(3000); console.log('app started at port 3000....');
咱们由上面的代码开始深刻到koa的源码:github
上面代码的开头引入koa框架,接着const app = new Koa();建立koa实例app,koa的构造函数很简单,以下:npm
constructor() { super(); this.proxy = false; this.middleware = []; //用来存放中间件 this.subdomainOffset = 2; this.env = process.env.NODE_ENV || 'development'; //运行环境 this.context = Object.create(context); //建立context对象 this.request = Object.create(request); //建立request对象 this.response = Object.create(response); //建立response对象 }
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函数首先判断参数是不是函数,不是就报错,而后判断这个函数是不是generator函数,若是是generator则须要转换一下,经过两个判断后,将这个函数push到middleware数组中保存.最后返回this(也是app实例)json
isten() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
listen函数里面调用http.createServe()建立http服务,关键是参数this.callback(),看一下代码:数组
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; }
首先,把全部middleware进行了组合,使用了koa-compose,咱们也不用去管他的内部实现,简单来讲就是返回了一个promise数组的递归调用。promise
这里的 const fn = compose(this.middleware);对应的调用代码以下:(查看koa-compose源码):cookie
return function (context, next) { // last called middleware #
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] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
return前面的做用是作参数检测,return后面也就是上面贴出来的代码才是咱们要关注的:app
运行流程:
i = 0 ==> index = 0 ==> fn = middleware[0] ==> return Promise.resolve(//若是fn里面存在await,则functions next()函数会被掉用, 则运行return dispatch(i + 1),以此类推)
知道中间件(固然通常指最后一个中间件)中不存在await或者全部的中间件都加载完毕(i === middleware.length) ,compose函数则最终返回.
例如:
const Koa = require('koa')
const app = new Koa()
app.use(async function (ctx, next) {
console.log('>> one');
await next();
console.log('<< one');
});
app.use(ctx => {
ctx.body='hello world gcy';
})
上面代码注册了两个中间件,通过const fn = compose(this.middleware)后返回:
fn的形式以下:
Promise.resolve(function(ctx) {
console.log('>> one');
return Promose.resolve(function(ctx){
ctx.body='hello world gcy';
}).then(() => {
console.log('<< one');
})
})
执行结果: 后台输出:>> one -- > 页面输出:hello world gcy --> 后台输出:<< one
接下来:
if (!this.listeners('error').length) this.on('error', this.onerror);
在处理http请求以前,koa会注册一个默认的错误处理函数,但咱们每次http请求错误其实是由ctx.onerror处理的:
const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
fn(ctx).then(() => respond(ctx)).catch(onerror)
ctx.onFinished 是确保一个流在关闭、完成和报错时都会执行相应的回调函数。onerror 就是咱们http请求错误处理函数.
咱们看看这个匿名函数,把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;
}
这里面都是一堆的组合和赋值,context,request, response相互挂载
值得注意的是:context.req/context.res 和context.request/context.response的区别,context.req/context.res表明nodejs的req和res对象,而context.request/context.response是koa的request和response对象
这个函数最后返回context,而后传入fn函数,此时fn函数被执行.
callback()函数中调用的respond函数里面不过是一些收尾工做,例如判断http code为空如何输出啦,http method是head如何输出啦,body返回是流或json时如何输出。
context.js
delegate(proto, 'request') //Request相关方法委托,从而让context做为调用入口
onerror(err) //中间件执行过程当中异常处理逻辑
request.js,response.js
分别对res和req进行了抽象和封装