最近一直都在开发基于node的先后端项目,分享一下Koa的源码。javascript
Koa本身的说法是next generation web framework for node.jsjava
Koa算是比较主流的node的web框架了,前身是express。相比于express,koa去除了多余的middleware,只留下了最基本的对node的网络模块的继承和封装,而且提供了方便的中间件调用机制,Koa的源码总共加起来就1600+,很快就能够看完。node
在分析koa的源码以前须要先了解一下node的http模块。git
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader(‘Content-Type’, ‘text/plain’);
res.end(‘Hello World’);
}
server.listen(3000);
复制代码
node的http模块主要负责了node对HTTP处理的封装。以上这段代码启动了一个监听3000端口的web server,而且返回'Hello World'给接收到的请求。github
每一次接收到一个新的请求的时候会调用回调函数,参数req和res分别是请求的实体和返回的实体,操做req能够获取收到的请求,操做res对应的是将要返回的packet。web
若是你须要对接收到的请求进行一系列处理的话,则须要按顺序写在回调函数里面。express
一样的功能对应的Koa的写法以下:后端
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
复制代码
这里的user其实是Koa的中间件机制提供的一个方便的处理请求的接口。请求会被use后面的函数依次按照use的顺序被处理,一般称这些函数为中间件,他们的参数ctx为koa基于node的http模块的req和res封装的一个对象,集合了req和res的功能为一体,而且增长了一些简单的操做。能够经过ctx.req和ctx.res获取到原生的req和res,与此同时ctx.request和ctx.response是Koa基于req和res封装的拥有一些新的功能的请求和返回的实体。 如下代码是Koa中间件使用的栗子:api
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log(‘pre1’)
await next();
console.log(‘post1’);
});
app.use(async (ctx, next) => {
console.log(‘pre2’);
await next();
console.log(‘post2’)
});
app.use(async ctx => {
console.log(‘pre3’)
ctx.body = 'Hello World';
console.log(‘post3’);
});
app.listen(3000);
复制代码
next()表示将请求的处理交给下一个中间件。若是没有next(),在该中间件函数执行结束后,将返回执行上一个中间件的next()后续的内容直到最开始的中间件的next()后面的内容执行完毕。数组
上面的代码的结果是
pre1
pre2
pre3
post3
post2
post1
复制代码
执行的结果和函数递归调用何其类似,以后了解了Koa的中间件机制后天然会明白这个结果的缘由。
在了解了koa的基本的使用和带着以上中间件执行的结果,咱们来看看koa的源码吧。
对Koa的理解主要分为两个部分:
按照【栗子】代码的顺序从上到下:
const app = new Koa();
复制代码
对应的源码中
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;
}
复制代码
将中间件函数push到middleware数组中。
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
复制代码
回调函数:
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;
}
复制代码
这里的compose函数是实现Koa的中间件机制的地方以后再细说。
const ctx = this.createContext(req, res);
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实际上只是建立以前说的集合了req & res & request & response的一个对象,做为参数传递给中间件。 对于request和response其中除了一些拓展的方便的接口以外大部分都是直接继承的http的req和response。 实现的新的接口也是比较简单的封装,举个栗子(response.js):
set status(code) {
assert('number' == typeof code, 'status code must be a number');
assert(statuses[code], `invalid status code: ${code}`);
assert(!this.res.headersSent, 'headers have already been sent');
this._explicitStatus = true;
this.res.statusCode = code;
this.res.statusMessage = statuses[code];
if (this.body && statuses.empty[code]) this.body = null;
},
/** * Get response status message * * @return {String} * @api public */
get message() {
return this.res.statusMessage || statuses[this.status];
}
复制代码
以上是response中实现的两个新的接口,实际上也就是res的接口再简单封装了一下而后返回。 再看(context.js)的最底部
/** * Response delegation. */
delegate(proto, 'response')
.method('attachment')
.method('redirect')
.method('remove')
.method('vary')
.method('set')
.method('append')
.method('flushHeaders')
.access('status')
.access('message')
.access('body')
.access('length')
.access('type')
.access('lastModified')
.access('etag')
.getter('headerSent')
.getter('writable');
/** * Request delegation. */
delegate(proto, 'request')
.method('acceptsLanguages')
.method('acceptsEncodings')
.method('acceptsCharsets')
.method('accepts')
.method('get')
.method('is')
.access('querystring')
.access('idempotent')
.access('socket')
.access('search')
.access('method')
.access('query')
.access('path')
.access('url')
.getter('origin')
.getter('href')
.getter('subdomains')
.getter('protocol')
.getter('host')
.getter('hostname')
.getter('URL')
.getter('header')
.getter('headers')
.getter('secure')
.getter('stale')
.getter('fresh')
.getter('ips')
.getter('ip');
复制代码
delegate的做用是将对应的对象上的method,getter,setter继承到另外一个对象上。 能够看到,直接继承了大部分req和res的方法。
接下来就是看看Koa的中间件机制的实现了。
compose的源码也是很是简单的:
'use strict'
/** * Expose compositor. */
module.exports = compose
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/** * @param {Object} context * @return {Promise} * @api public */
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)
}
}
}
}
复制代码
能够看到其实就是按照顺序将middleware数组中的中间件s按照顺序递归执行,每次执行next()的时候就是执行下一个中间件,最后一个next也就是第一个中间件的第二个参数,由于是undefined,因此会结束递归调用反向依次执行每一个中间件next后续的代码。每次return的都是一个Promise对象,所以咱们写的时候是await来等待这个异步调用的结束,而后执行下一个中间件。而咱们通常的写法是将await next()写在中间件函数的最后,从而用尾递归的方式来实现每一个请求依次被中间件函数处理的效果。
恩,Koa的主要的概念就是这些,它的目的就是一个极简的框架,只提供最基本的接口,大部分的功能,开发者根据需求使用use添加中间件来实现。